Hey guys, I’m trying to get this working (any asso...
# getting-started
t
Hey guys, I’m trying to get this working (any associated design pattern?). Given a set of known inputs, I wanted to take advantage of sealed classes to be sure any input has an associated handler (no matter what kind of output it returns). But I run into some troubles, my handler does not satisfy the type and I don’t understand why.
Copy code
sealed class Input
data object InputTypeA : Input()

interface Output
data object OutputTypeA : Output

interface Handler<in I : Input, out O : Output> {
    fun handle(input: I): O
}

class HandlerA : Handler<InputTypeA, OutputTypeA> {
    override fun handle(input: InputTypeA): OutputTypeA = OutputTypeA
}

class Controller(private val handlerA: HandlerA) {

    fun <I : Input, O : Output> selectHandler(input: I): Handler<I, O>? =
        when (input) {
            /*
             * Type mismatch.
             * Required:
             * Handler<I, O>?
             * Found:
             * HandlerA
             */
            is InputTypeA -> handlerA
            else -> null
        }
}
s
You can’t return the handler from this generic function because you can’t prove that it is a
Handler<I, O>
. You certainly can’t prove anything about
O
, so you should remove that parameter from the function signature: it doesn’t do anything. But you can’t prove that
handlerA
is a
Handler<I, *>
either. The problem arises from the fact that the actual value of the generic type is not necessarily the exact type of the value parameter. Determining the type of
input
does not actually tell us the value of
I
. For example, consider that a consumer would be able to call
controller.handleInput<Input>(someInputA)
and get a result of type
Handler<Input, *>
, even though the actual handler can only accept
InputTypeA
. My advice would be to make a
selectAndHandle
function that both selects and invokes the handler.
🙌 1
Since you asked about design patterns: selecting and invoking a different function depending on the type of an input parameter is known as multiple dispatch. In Java, where there are once were no sealed classes, you sometimes see the visitor pattern being used to solve this type of problem. There is, in fact, a solution using the visitor pattern, but it’s pretty gross and you will probably have to spend some time studying the pattern in order to understand it 😄
Copy code
interface Input<in I : Input<I>> {
    fun getHandler(controller: Controller): Handler<I>
}

data object InputTypeA : Input<InputTypeA> {
    override fun getHandler(controller: Controller): Handler<InputTypeA> = controller.getHandler(this)
}

data object InputTypeB : Input<InputTypeB> {
    override fun getHandler(controller: Controller): Handler<InputTypeB> = controller.getHandler(this)
}

interface Handler<in I : Input<I>> {
    fun handle(input: I)
}

class Controller(
    private val handlerA: Handler<InputTypeA>,
    private val handlerB: Handler<InputTypeB>
) {
    fun <I: Input<I>> getHandler(input: Input<I>): Handler<I> = input.getHandler(this)

    fun getHandler(input: InputTypeA): Handler<InputTypeA> = handlerA
    fun getHandler(input: InputTypeB): Handler<InputTypeB> = handlerB
}
(I don’t recommend this)
tl;dr: just do this:
Copy code
class Controller(private val handlerA: HandlerA) {
    fun handle(input: Input): Output? =
        when (input) {
            is InputTypeA -> handlerA.handle(input)
            else -> null
        }
}
t
Hi Sam, thanks for explanations! did read; not too long 😉. I had thought about visitor indeed, but as you said, pretty gross and seems not to be best approach I like your suggestion to both select and handle in the same function, but I’m pretty lazy, so I would have hoped to be able to do something like
Copy code
fun selectAndHandle(input: Input): Output =
   when (input) {
        is InputTypeA -> handlerA
        is InputTypeB -> handlerB
   }.handle(input) // But obviously this is not gonna work!
Any thought? 🤔
s
I can’t think of a way to make that work, because the smart-cast for
input
is only applied inside the relevant branch of the
when
statement
Once you exit the
when
block, you go back to having an unknown type of input again, at least as far as the compiler is concerned
t
Yes exactly, I’m doomed