user3816466
user3816466

Reputation: 85

Mixin in a trait parameterized with Enumeration into Enumeration

I'm trying to add a function apply to my Enumerations. I want to map enumerated values to functions and later on use it like this:

Action(DO_THIS, "Arg string")

which should print it's value. I have multiple enum classes and I'm trying to implement this functionality once via a trait mixin. So far I've failed to make compiler happy. I have this code that does not compile:

trait EnumFunc [EnumType <: Enumeration] {
  protected def funcMappings: Map[EnumType#Value, Function1[String, Unit]]

  def apply(enum: EnumType#Value, arg: String): Unit =
    funcMappings.getOrElse(enum, (arg: String) => ())(arg)
}


trait Action extends Enumeration with EnumFunc[Action] {
  type Action = Value

  val DO_THIS, DO_THAT = Value

  override val funcMappings: Map[Action, Function1[String, Unit]] =
    Map(DO_THIS -> ((arg: String) => println(arg)))
}

object Action extends Action

The compiler produces this error:

error: overriding method funcMappings in trait EnumFunc of type => Map[Action#Value,String => Unit];
 value funcMappings has incompatible type
         override val funcMappings: Map[Action, Function1[String, Unit]] =

I can't figure out exactly what the problem is. Could someone please explain if it's possible to achieve this at all and why this does not compile?

Upvotes: 2

Views: 250

Answers (1)

wingedsubmariner
wingedsubmariner

Reputation: 13667

Value inside of Action and EnumType#Value are not quite the same. Value inside of Action is a path-dependent type, it can only refer to instances of Values that have this as their parent, i.e. Value is a synonym for this.Value. EnumType#Value is a type projection, it can refer to any value from an instance of EnumType, and doesn't care what it's parent is. So the declaration of funcMappings and apply in Action are in fact being given stricter parameter types, which is not type-safe.

However, the type parameter on EnumType isn't actually needed, if you extend Enumeration you can use Value directly:

trait EnumFunc extends Enumeration {
  protected def funcMappings: Map[Value, Function1[String, Unit]]

  def apply(enum: Value, arg: String): Unit =
    funcMappings.getOrElse(enum, (arg: String) => ())(arg)
}

and then it will work. However, this is just one of the many examples of how Scala's Enumeration is an abomination. Once you know you want to add methods to the individual elements, it's much better to use a hierarchy of case classes/objects:

abstract class Doers {
  def apply(s: String): Unit
}
case object DoThis extends Doers {
  def apply(s: String): Unit = println(s)
}
case object DoThat extends Doers {
  def apply(s: String): Unit = ???
}

Upvotes: 1

Related Questions