Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Putting a generic lambda into a map

Ok so the code below is an event system that does the following:

  1. Assigns an integer id to a lambda expression
  2. Puts the lambda's id in an event's mutable set
  3. Maps the integer ID to the lambda expression
  4. Returns the id (can be used later to remove events from the lambda)

The code is as follows:

class EventHandler {
    companion object {
        val handlers = HashMap<KClass<out Event>, MutableSet<Int>>()
        val idMap = HashMap<Int, (Event) -> Unit>();

        /**
         * @param event     Class of the event you are registering
         * @param handler   What to do when the event is called
         */
        fun <T : Event> register(event: KClass<T>, handler: (T) -> Unit): Int {
            var id: Int = 0;
            while(idMap[id] != null) {
                id++;
            }
            var list = handlers.getOrPut(event, {mutableSetOf()});
            list.add(id);
            idMap[id] = handler;
            return id;
        }
    }
}

The intended use of this method would be something like this:

EventHandler.register(ChatEvent::class) { onChat ->
    println(onChat.message)
}

There is an error at the following line: idMap[id] = handler;

The error is because the handler is of type (T) -> Unit, although it needs to be (Event) -> Unit in order to add it to the idMap. Although I said that T should extend Event when I created it, so this shouldn't be a problem. Does anyone know why this happens of if there is a solution?

like image 316
Deanveloper Avatar asked Feb 08 '23 12:02

Deanveloper


2 Answers

The problem comes from the fact that idMap takes a function that receives an Event - any kind of Event. Then you try to register a function that takes a specified subclass of Event, which, when you pull it back out, the compiler won't be able to tell what possible subclass it receives. Yes, you're storing the specific type in your other map, but the compiler can't use that.

I do not believe you can create the mapping system that you want to create. Not without a few more layers of indirection or abstraction...

like image 173
Jacob Zimmerman Avatar answered Feb 11 '23 16:02

Jacob Zimmerman


The reason you get this error is explained well by @jacob-zimmerman in his answer: (T) -> Unit is not a subtype of (Event) -> Unit (on the contrary it is a supertype).

You could make an unchecked downcast to required function type:

idMap[id] = handler as (Event) -> Unit

But then before invoking such handler you must check that an event has type that the handler could accept, for example by querying handler from map based on the type of the event:

fun invoke(event: Event) {
    val kclass = event.javaClass.kotlin
    val eventHandlers = handlers[kclass]?.map { idMap[it]!! } ?: return
    eventHandlers.forEach { it.invoke(event) }
}
like image 36
Ilya Avatar answered Feb 11 '23 16:02

Ilya