2
\$\begingroup\$

Here is a composable IHandler<T> implementation. Any ideas on how to make it shorter and get rid of exception being thrown?

interface Handler<T> {
    fun handle(e: T)

    operator fun <TOther> plus(other: Handler<TOther>): Handler<Any> =
        Composite(this, other)

    private class Composite<T1, T2>(
        private val left: Handler<T1>, 
        private val right: Handler<T2>) : Handler<Any> {
        override fun handle(e: Any) {
            try { left.handle(e as T1) } catch(ex: ClassCastException) { }
            try { right.handle(e as T2) } catch(ex: ClassCastException) { }
        }
    }
}

Smoke test looks like this:

val h = A() + B()
h.handle("33") // prints 'String: 33'
h.handle(33)   // prints 'Int: 33'

where:

class A : Handler<String> {
    override fun handle(e: String) = Debug.println("String", e.toString())
}

class B : Handler<Int> {
    override fun handle(e: Int) = Debug.println("Int", e.toString())
}
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

Well if you don't want the jvm to keep track of types for you by catching exceptions, you can do it yourself. I imagine its much faster than waiting for ClassCastExceptions but I didn't benchmark at all:

interface Handler {
    fun handle(e: Any)
    fun handlesType(t: Class<*>): Boolean
    
    operator fun plus(other: Handler): Handler =
        Composite(this, other)

    private class Composite(
        private val left: Handler,
        private val right: Handler
    ) : Handler {
        override fun handle(e: Any) {
            if (left.handlesType(e::class.java)) {
                left.handle(e)
            } else if (right.handlesType(e::class.java)) {
                right.handle(e)
            }
        }

        override fun handlesType(t: Class<*>) = 
            left.handlesType(t) || right.handlesType(t)
    }
}

inline fun <reified T> handler(crossinline fn: (T) -> Unit) = (object : Handler {
    override fun handle(e: Any) { fn(e as T) }
    override fun handlesType(t: Class<*>) = T::class.java.isAssignableFrom(t)
})

val A = handler<String> { println("String: $it") }
val B = handler<Int> { println("Int: $it") }

val h = A + B
\$\endgroup\$
1
\$\begingroup\$

Using inline fun and reified it's actually possible to check if a value is of a type parameter before using it.

Now, I had to refactor this a bit because of some constraints:

  • Check for a is T can only be done if T is reified
  • Only inline function can have reified type parameters
  • Virtual functions (e.g. interface methods) cannot be inline

So for me it was a bit of a puzzle to see how it could be done, but it could be done using something like this:

class Handler<T>(val function: (T) -> Unit) {
    inline operator fun <reified TOther> plus(other: Handler<TOther>): Handler<Any> = Handler {
        if (it is TOther) other.function(it)
        else this.function(it as T)
    }
}

Even if this is not the end result that you end up with, maybe it can help you explore future possibilities.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.