Daniel Webster
Daniel Webster

Reputation: 31

Declaring Function Literals with generic input parameters in Kotlin

I don't know how to specify a function literal where the input parameter can covary so that the function literal can have assigned to it functions that accept subtypes of the input type.

The simplest example I can come up with is that i want to do something like this:

var f: (Number) -> Unit = { number: Number -> println(number) }
f = {int: Int -> println(number) } // <-- this does not compile

These declarations do not work:

var f: (in Number) -> Unit = {number: Number ->} // doesn't compile
var f: (out Number) -> Unit = {number: Number ->} // doesn't compile
var f: <N: Number> (N) -> Unit = {number: Number ->} // ridiculous

Here is the context of what I ACTUALLY want to do. I want to create a simple event handler class.

This is my Kotlin code:

class EventHandler() {
  private val NO_OP = {event: Event -> }
  private val handlerMap = 
      mutableMapOf<KClass<out Event>, (Event) -> Unit>() // here is the problem declaration

  fun <E: Event> registerHandler(
    eventClass: KClass<out E>, 
    handler: (E) -> Unit) {

    handlerMap[eventClass] = handler // this doesn't compile
  }

  fun handle(event: Event) = getHandler(event).invoke(event)

  fun getHandler(event: Event): (Event) -> Unit = 
     handlerMap[event::class] ?: NO_OP
}

The handlerMap[eventClass] = handler doesn't compile, because the handlerMap accepts as values (Event) -> Unit, and the type of the handler is (E) -> Unit where E is a type parameter that extends Event (<E: Event>).

The Error message is:

Events.kt:[18,9] Type inference failed: Cannot infer type parameter V in operator inline fun <K, V> MutableMap<K, V>.set(key: K, value: V): Unit

None of the following substitutions
    receiver: MutableMap<KClass<out Event>, (Event) -> Unit>  arguments: (KClass<out Event>,(Event) -> Unit)
    receiver: MutableMap<KClass<out Event>, (E) -> Unit>  arguments: (KClass<out Event>,(E) -> Unit)
can be applied to
    receiver: MutableMap<KClass<out Event>, (Event) -> Unit>  arguments: (KClass<out E>,(E) -> Unit)

I am using kotlin-maven-plugin:1.1-M03.

Upvotes: 0

Views: 455

Answers (1)

Daniel Webster
Daniel Webster

Reputation: 31

Ok, I didn't think of this before, but it looks like you can just cast the Function to the type of the literal.

In other words, you can do this

var f: (Number) -> Unit = { number: Number -> println(number) }
f = {int: Int -> println(number) } as (Number) -> Unit

For my actual usecase, I would make the registerHandler function look like this:

fun <E: Event> registerHandler(eventClass: KClass<out E>, handler: (E) -> Unit) {
    handlerMap[eventClass] = handler as (Event) -> Unit
}

Upvotes: 1

Related Questions