views:

81

answers:

2

I think this is about covariance but I'm weak on the topic...

I have a generic Event class used for things like database persistance, let's say like this:

class Event(
  subject: Long,
  verb: String,
  directobject: Option[Long],
  indirectobject: Option[Long],
  timestamp: Long)
{
  def getSubject = subject
  def getVerb = verb
  def getDirectObject = directobject
  def getIndirectObject = indirectobject
  def getTimestamp = timestamp
}

However, I have lots of different event verbs and I want to use pattern matching and such with these different event types, so I will create some corresponding case classes:

trait EventCC
case class Login(user: Long, timestamp: Long) extends EventCC
case class Follow(
  follower: Long,
  followee: Long,
  timestamp: Long
) extends EventCC

Now, the question is, how can I easily convert generic Events to the specific case classes.

This is my first stab at it:

def event2CC[T <: EventCC](event: Event): T = event.getVerb match {
  case "login" => Login(event.getSubject, event.getTimestamp)
  case "follow" => Follow(
    event.getSubject,
    event.getDirectObject.getOrElse(0),
    event.getTimestamp
  )
  // ...
}

Unfortunately, this is wrong.

<console>:11: error: type mismatch;
 found   : Login
 required: T
             case "login" => Login(event.getSubject, event.getTimestamp)
                             ^
<console>:12: error: type mismatch;
 found   : Follow
 required: T
             case "follow" => Follow(event.getSubject, 
event.getDirectObject.getOrElse(0), event.getTimestamp)

Could someone with greater type-fu than me explain if, 1) if what I want to do is possible (or reasonable, for that matter), and 2) if so, how to fix event2CC. Thanks!

+5  A: 

It seems to me that the best thing you can return is EventCC:

def event2CC(event: Event): EventCC

The type of T cannot be made more specific at compile time. It's only at run-time that we know if T is precisely Login or Follow and this only depends on event values.

Eric
Yeah, I think you're probably right. But then how can you access the fields unique to each case class? Doing a pattern match on the case class type?`event2CC(myEvent) match { case f: Follow => f.followee }`
pr1001
Yes you can definitely do that.
Eric
+1  A: 

If you want to be able to use Events in pattern matching, you could define an extractor for them:

object Event {
  def unapply(evt: Event): Some((Long, String, Option[Long])) = 
    Some(evt.getSubject, evt.getVerb, evt.getDirectObject)
}

val evt: Event = retrieveEventFromEther()
evt match {
  case Event(_, "login", _) => "It was a login!"
  case Event(_, "follow", Some(_)) => "It was a follow with a direct object!"
}
David Winslow
True, but then I lose the mappings to parameters relevant to specific event types. For instance, I would need to remember when accessing an event whether the `followee` is stored in the `subject` field or the `directobject` one, rather than just doing `aFollow.followeee`.
pr1001
You can make more than one extractor to pull values from the same source.
David Winslow

related questions