Defining a generic List

Problem: I want to define a List of Tuple2 and apply the elements of the tuples to the following Java method:

<T extends Event> void addEventHandler(EventType<T> type,
                                       EventHandler<? super T> handler)

The first element of the tuple is applied to type (EventType<T>), and the second element to handler (EventHandler<? super T>).

I need to be able to add every variant of EventType<T> and EventHandler<? super T> which can be applied to addEventHandler to the list and need to retrieve them as types that can be applied to addEventHandler.

Example to illustrate the use case:

val eventType1: EventType[KeyEvent] = EventType1()
val eventType2: EventType[MouseEvent] = EventType2()

val eventHandlers: List[???] = List(
  (eventType1, (event: KeyEvent) => ...),
  (eventType2, (event: MouseEvent) => ...))

eventHandlers.foreach(item => addEventHandler(item._1, item._2))

Sorry, I forgot that the type of the list must be a Tuple (EventType[T], EventHandler[_ >: T]), but this should not change your proposed solution, right?

protected def eventHandlers[T]: List[(EventType[T], EventHandler[_ >: T])] = Nil

Now, if I want to iterate over the List:

eventHandlers.foreach(handler => doSomething(handler))

IntelliJ states that handler is of type Nothing. Is this again a problem of IntelliJ’s type checker or what’s going on there?

but this should not change your proposed solution, right?

Right, it was a typo from my side, I just fixed it, sorry.

In this case it is right, since you didn’t specify the type of T.


Maybe we are already too deep to see the problem, let’s take a step back.
What exactly do you want to model and how do you want to use it?

Well, you took my type, I edited when I saw that I forgot the () :slight_smile:

I won’t to model a List that can hold Tuples which elements can be applied to this Java method:

public final <T extends Event> void addEventHandler(
            final EventType<T> eventType,
            final EventHandler<? super T> eventHandler) { ... }

You’d have to somehow let it know what T is. For example, if T was String, you could write:

eventHandlers[String].foreach(handler => doSomething(handler))

But I don’t know what type T is since I want to apply all possible variants.

So I take that given the list you will call foreach on it.

But who will provide that List, like I do not understand why you are defining it like a def, it is to be overridden through inheritance?

I could do something like that:

protected def eventHandlers[T <: Event]: List[(EventType[T], EventHandler[_ >: T])] = Nil

But if I than try to apply this to the Java method I get a type miss-match.

Capture

Yes, sub-classes should be able to override this method in order to provide a list of event handlers.

An existential type might work, if this is Scala 2 we’re talking about.

List[(EventType[T], EventHandler[_ >: T]) forSome { type T }]

In Scala 3 I guess you would have to create a custom class.

case class EventTuple[T](_1: EventType[T], _2: EventHandler[_ >: T])
List[EventTuple[_]]

But then why wouldn’t the subclass know the type of T - either because it’s hardwired to that type or because it has a generic parameter of its own to specify it?

Just to make sure: Is there one specific T for all elements of that list, or do you want to have a list of pairs with independent, arbitrarily varying event types? In this case, you’ll probably have to either drop generic parameters in general and switch to abstract type members, or look into some advanced type magic like HLists.

2 Likes

I believe something like this should work:

trait ScalaEventHanlder {
  type E <: Event
  def eventType: EventType[E]
  def handler(e: E): Unit
}

trait ScalaApi {
  def eventHandlers: List[ScalaEventHanlder]
}

object ScalaApi {
  def run(impl: ScalaApi): Unit =
    impl.eventHandlers.foreach { scalaEventHandler =>
      JavaApi.addEventHandler[scalaEventHandler.E](
        scalaEventHandler.eventType,
        scalaEventHandler.handler
      )
    }
}

But I can’t test the Java parts, so I mocked them in Scala which may change the semantics.


See the whole code running here.

1 Like

T can be every sub-type of Event, and must be the same type for both elements of the Tuple.

…but the types of T for the pairs can differ among elements of the list? Then there simply is no T and you’ll have to resort to mechanisms beyond generics. @BalmungSan has already posted a draft based on abstract type members. Similar, just a bit closer to the Java API (and thus more convoluted):

trait Eventful {
  type E <: Event
  type H >: E <: Event
  val tp: EventType[E]
  val hnd: EventHandler[H]
}

def addEventful(ef: Eventful): Unit = addEventHandler(ef.tp, ef.hnd)

eventHandlers.foreach(addEventful)

[EDIT: added Event subtype constraint to H]

2 Likes

Yes, but it was OK if they are all just Event. My only requirement is, that I can add Tuples having any sub-type of Event and get out Tuples where the generic type is Event.

Since I apply the elements of the list to a method that requires T must extend Event it should accept as well Event itself.

In other words: I never need to access the element in the list with the actual type, if that makes sense.

Thank you all so much for your help so far.

I now edited my initial post that it states my exact use case and I added as well a pseudo example to illustrate what I want to achieve.

Assuming a Scala API with covariant generic parameters…

trait EventType[+T <: Event]
trait EventHandler[+T <: Event]

…you could get away with List[(EventType[Event], EventHandler[Event])].

Java doesn’t offer this kind of covariance, though, so I fear you’ll still have to go with abstract type members (or some other workaround). There still is no shared T for the list elements.

val eventHandlers: List[(T, EventHandler<T>) forSome { type T <: Event}] = List(
  (eventType1, (event: KeyEvent) => ...),
  (eventType2, (event: MouseEvent) => ...))

eventHandlers.foreach(item => addEventHandler(item._1, item._2))

should work in scala 2

1 Like

Did my Scastie work? If it doesn’t why not? Can you add the error here?

1 Like

I did not have the chance to try it out. I modified my original post so that everybody is on the same page and knows what my use case is and others can learn form what you all posted here, too :slight_smile:

When I am home I will try your implementation for sure, thank you for your effort!