Question

I am implementing a GUI event system in Scala. I have something like:

case class EventObject
case class KeyEventObject extends EventObject
case class MouseEventObject extends EventObject

I would like to store event listener closures in a (multi-)map, like so:

var eventListeners = new MultiMap[EventDescriptor, (EventObject => Unit)];

My question is, is there some way to rewrite this so that the function signatures of the stored closure can be EventObject or any subclass? Something like the following:

var eventListeners = new MultiMap[EventDescriptor, [A <: EventObject](A => Unit)]

so that I can have the subtype known when I define the listener functions:

eventListeners.put(KEY_EVENT, (e:KeyEventObject) => { ... })
eventListeners.put(MOUSE_EVENT, (e:MouseEventObject) => { ... })
Was it helpful?

Solution

Not that many things are impossible. You can do the following, for example, with type classes:

class HMultiMap {
  import scala.collection.mutable.{ Buffer, HashMap }

  type Mapping[K, V]

  private[this] val underlying = new HashMap[Any, Buffer[Any]]

  def apply[K, V](key: K)(implicit ev: Mapping[K, V]) =
    underlying.getOrElse(key, Buffer.empty).toList.asInstanceOf[List[V]]

  def add[K, V](key: K)(v: V)(implicit ev: Mapping[K, V]) = {
    underlying.getOrElseUpdate(key, Buffer.empty) += v
    this
  }
}

And now:

sealed trait EventObject
case class KeyEventObject(c: Char) extends EventObject
case class MouseEventObject(x: Int, y: Int) extends EventObject

sealed trait EventDescriptor
case object KEY_EVENT extends EventDescriptor
case object MOUSE_EVENT extends EventDescriptor

class EventMap extends HMultiMap {
  class Mapping[K, V]

  object Mapping {
    implicit object k extends Mapping[KEY_EVENT.type, KeyEventObject => Unit]
    implicit object m extends Mapping[MOUSE_EVENT.type, MouseEventObject => Unit]
  }
}

It's a little messy, but the usage is much prettier:

val eventListeners = new EventMap

eventListeners.add(KEY_EVENT)((e: KeyEventObject) => println(e.c))
eventListeners.add(MOUSE_EVENT)((e: MouseEventObject) => println("X: " + e.x))
eventListeners.add(KEY_EVENT)((e: KeyEventObject) => println(e.c + " again"))

We can confirm that we can pick out individual kinds of event handlers:

scala> eventListeners(KEY_EVENT).size
res3: Int = 2

And we can pretend to fire an event and run all the handlers for it:

scala> eventListeners(KEY_EVENT).foreach(_(KeyEventObject('a')))
a
a again

And it's all perfectly safe, since nothing gets into the underlying loosely-typed map without the proper evidence. We'd get a compile-time error if we tried to add a function from String to Unit, for example.

OTHER TIPS

EDIT

Seems impossible. Also a case-to-case inheritance gets prohibited in Scala-2.10

...

Maybe by using some specific trait and declaring event object classes sealed and extend that trait?

val eventListeners = new MultiMap[EventDescriptor, ((_ >: EventTrait) => Unit)]

From List sources:

:+[B >: A, That](elem: B)(implicit bf: CanBuildFrom[List[A], B, That]): That

That - is a specific type, it could be built from your EventTrait by implicit builder, one builder for each event type. Instead of elem: B try use classOf[B]. Create get methods that will access your MultiMap using different classOf:

def getMouseEvent(ed: EventDescriptor) = multimap.entrySet.filter(
(a, b) => a == ed ).map((a, b) => (a, convertTo(ClassOf[MouseEvent], b)).
filter((a, b) => b != null)

convertTo returns null if it could not convert event to appropriate type

Ugly.

It's impossible.

Let's suppose you have such Map:

val f = eventListeners(key).head

How would you call the function f? With parameter of type EventObject? You can't. It could be KeyEventObject => Unit. With parameter of type KeyEventObject? You can't. It could be MouseEventObject => Unit.

You can use PartialFunction[EventObject, Unit] and check for isDefinedAt every time, but it's an ugly way.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top