it4rb
it4rb

Reputation: 166

how to enfore ADT with same type parameter

I have following ADT:

sealed trait Event
case class EventA(id: Int) extends Event
case class EventB(id: Int) extends Event
case object EventDontCare extends Event

object Event {
  def test(ev: Event) = ev match {
    case EventA(x)     => x
    case EventB(y)     => y + 1
    case EventDontCare => 0
  }
}

val eva = EventA(10)
println(Event.test(eva))

It worked well but now I need to have 2 separated types, 1 use Int as id as above, the other use String as id. I've tried to add type parameter to Event trait:

sealed trait GEvent[ID]
case class GEventA[ID](id: ID) extends GEvent[ID]
case class GEventB[ID](id: ID) extends GEvent[ID]

object EventInt {
  type Event = GEvent[Int]
  type EventA = GEventA[Int]
  type EventB = GEventB[Int]
  case object EventDontCare extends GEvent[Int]

  def test(ev: Event) = ev match {
    case x: EventA     => x.id
    case y: EventB     => y.id + 1
    case EventDontCare => 0
  }
}
object EventString {....}

val evi = new EventInt.EventA(10)
val evii = GEventA[Int](10)
val evd = EventInt.EventDontCare
println(EventInt.test(evd))
println(EventInt.test(evi))
println(EventInt.test(evii))

There are several questions I would like to ask:

  1. Is there a better way to inject a type to all members of an ADT? I don't feel satisfied with above approach.
  2. In the pattern match in test method, why can't I use case EventA(x) => or event case GEventA[Int](x) =>? Similarly, why must I create evi variable with the new keyword?
  3. Even I've covered all 3 case, why does the compiler still warn me:

    match may not be exhaustive. It would fail on the following input: EventDontCare

    But it still run correctly (print out 10 for the DontCare case)

Upvotes: 0

Views: 338

Answers (1)

marios
marios

Reputation: 8996

I think what you have there is correct. I suggest you define your ADT together (now you have EventDontCare defined separately). Also, since the base trait Event doesn't have an id it's pointless to enforce the type there. So I would re-write your ADT as:

sealed trait Event
case class GEventA[ID](id: ID) extends Event
case class GEventB[ID](id: ID) extends Event
case object EventDontCare extends Event

The pattern matching can be re-written like this to include the type on the members.

def test(ev: Event) = ev match {
   case GEventA(id:Int) => id
   case GEventB(id:Int) => id + 1
   case EventDontCare   => 0
}

Typing this, I don't get the warning you mentioned.

Finally, what you observed with type aliases is how things work in Scala. If you define a type alias for a case class you cannot use the alias to define an instance without using new.


(optional section)

Here is an alternative definition of the ADT that decouples the id from the rest of your entities.

sealed trait Event
case object GEventA extends Event
case object GEventB extends Event
case object EventDontCare extends Event
case class IdEvent[A, E <: Event](id: A, e: E) extends Event

The benefit here is that you don't "pollute" all your entities with the details of the id.

Upvotes: 1

Related Questions