I have defined the following function:
inline fun <T> T.tryTo(block: T.() -> Unit): T? {
try {
block()
} catch (ex: IllegalArgumentException) {
return this
}
return null
}
The purpose is to build a chain of try-actions on an object, e.g.:
val input: String = getInput();
input.tryTo /* treat as a file name and open the file */ {
Desktop.getDesktop().open(File(this))
}?.tryTo /* treat as a number */ {
try {
doSomethingWithTheNumber(parseInt(this))
} catch (ex: NumberFormatException) {
throw IllegalArgumentException()
}
}?.tryTo {
println("All options tried, none worked out. Don't know how to treat this input.")
}
So far that works fine.
But, as you can see in the middle tryTo-block ("treat as a number"), it is inconvenient to rethrow an "expected" exception as an IllegalArgumentException to keep the schema working. It would be nicer to write:
val input: String = getInput();
input.tryTo<IllegalArgumentException> /* treat as a file name and open the file */ {
Desktop.getDesktop().open(File(this))
}?.tryTo<NumberFormatException> /* treat as a number */ {
doSomethingWithTheNumber(parseInt(this))
}?.tryTo<Exception> {
println("All options tried, none worked out. Don't know how to treat this input.")
}
So, I have rewritten the function tryTo to:
inline fun <T, X: Exception> T.tryTo(block: T.() -> Unit): T? {
try {
block()
} catch (ex: X) {
return this
}
return null
}
Unfortunately, the latter does not compile: "Type parameter is forbidden for catch parameter".
How to circumvent this restriction?
Addendum:
Now I've got it to:
inline fun <T, reified X: Exception> T.tryTo(block: T.() -> Unit): T? {
try {
block()
} catch (ex: Exception) {
return if (ex is X) this else throw ex
}
return null
}
But I'm still not happy with this because it requires me to specify both types explicitly ("Type inference failed..." / "2 type arguments expected ..."):
input.tryTo<String, IllegalArgumentException> /* treat as a file in the stapel-directory */ {
...
}
though the first type parameter is obvious as inferable from the receiver object.
I thought this would be possible if you just made the type parameter reified, but apparently it is not. I did find the source of this check, and it quite clearly errors for any sort of type parameter in a catch clause, whether it's reified or not.
The commit message that added these checks references this issue - apparently the catch clause with a type parameter was catching all thrown Exception
instances, and crashing with a ClassCastException
if the exception wasn't of the specified type.
A possible workaround for your case comes from this answer for the similar Java question - if the generic type is reified, you can check if the exception thrown was of that specific type, which I believe makes this function what you're looking for:
inline fun <T, reified X : Exception> T.tryTo(block: T.() -> Unit): T? {
try {
block()
} catch (ex: Exception) {
if (ex is X) {
return this
}
}
return null
}
Although the call site gets quite ugly because you can't just specify the second type parameter of a function call if it has two type parameters:
val input: String = getInput()
input.tryTo<String, IllegalArgumentException> /* treat as a file name and open the file */ {
Desktop.getDesktop().open(File(this))
}?.tryTo<String, NumberFormatException> /* treat as a number */ {
doSomethingWithTheNumber(parseInt(this))
}?.tryTo<String, Exception> {
println("All options tried, none worked out. Don't know how to treat this input.")
}
A slightly nicer alternative to the above, and closer to the original Java answer:
inline fun <T> T.tryTo(exceptionType: KClass<out Exception>, block: T.() -> Unit): T? {
try {
block()
} catch (ex: Exception) {
if (exceptionType.isInstance(ex)) {
return this
}
}
return null
}
With the KClass
instances passed in like so:
input.tryTo(IllegalArgumentException::class) /* treat as a file name and open the file */ {
Desktop.getDesktop().open(File(this))
}?.tryTo(NumberFormatException::class) /* treat as a number */ {
doSomethingWithTheNumber(parseInt(this))
}?.tryTo(Exception::class) {
println("All options tried, none worked out. Don't know how to treat this input.")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With