Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala: add methods to an enum

Tags:

enums

scala

I have a simple enum like this:

object ConditionOperator extends Enumeration {

  val Equal           = Value("equal")
  val NotEqual        = Value("notEqual")
  val GreaterOrEqual  = Value("greaterOrEqual")
  val Greater         = Value("greater")
  val LessOrEqual     = Value("lessOrEqual")
  val Less            = Value("less")

And I'd like to add a method to each enum so that I can use it like this:

def buildSqlCondition(field: String, operator: ConditionOperator.Value, value: String ) = {
  val sqlOperator = operator.toSql
  [...]

So, ConditionOperator.Equal.toSql wuld return "=", and ConditionOperator.NotEqual.toSql would return "<>", etc...

But I don't know how to define a toSql method, so that each enum can "see" it's own value and decide how to translate itself to a sql operator...

like image 632
opensas Avatar asked Dec 12 '22 22:12

opensas


2 Answers

This is an example of what I have found for Scala 2.9.2 from various searches on the topic in the past:

object Progress extends Enumeration {
    type enum = Value

    val READY = new ProgressVal {
      val isActive = false
      def myMethod: Any = { .. }
    }

    val EXECUTE = new ProgressVal {
      val isActive = true
      def myMethod: Any = { .. }
    }

    val COMPLETE = new ProgressVal {
      val isActive = false
      def myMethod: Any = { .. }
    }

    protected abstract class ProgressVal extends Val() {
      val isActive: Boolean
      def myMethod: Any
    }
    implicit def valueToProgress(valu: Value) = valu.asInstanceOf[ProgressVal]
}
type Progress = Progress.enum
  • The implicit is key to making this usable.

  • The type enum and type Progress are somewhat redundant; I include them to present both concepts as something I've found helpful.


To give credit where it's due, the original idea for this came from Sean Ross in a response to a question of which this one is a duplicate.

like image 135
Richard Sitze Avatar answered Dec 14 '22 10:12

Richard Sitze


You can start by defining an inner class that overrides Enumeration.Val. To simplify things, let's call it Value (we overload the original meaning of Value as defined in Enumeration). So we have our new Value type which inherits Enumeration.Val which itself inherits Enumeration.Value.

Given that toSql has no side effects and returns a different string for each enumeration, you might as well just make it a val.

Finally, to make it fully usable, you'll want to have ConditionOperator.apply and ConditionOperator.withName to return your newly defined Value class instead of the Value type as defined in Enumeration. Otherwise, when using apply or withName to look up an instance of ConditionOperator by index/name, you won't be able to call toSql because the enumeration type will not be specific enoough. Ideally we'd like to just override apply and withName and add a cast to ConditionOperator.Value, but these methods are final. However we can employ a small trick here: define new methods apply and withName with the same signature but an additional implicit parameter that will always be available (Predef.DummyImplicit fits this rolle perfectly). The additional parameter ensures that the signature is different so that we are able to define these new methods, while at the same time being nearly indistinguishable from the original apply/withName methods. The rules for overloading resolution in scala ensure that our new methods are the ones favored by the compiler (so we have in practice shadowed the original methods).

object ConditionOperator extends Enumeration {
  // Here we overload the meaning of "Value" to suit our needs
  class Value(name: String, val toSql: String) extends super.Val(name) {
    def someFlag: Boolean = true // An example of another method, that you can override below
  }
  val Equal           = new Value("equal", "=")
  val NotEqual        = new Value("notEqual", "<>")
  val GreaterOrEqual  = new Value("greaterOrEqual", ">=")
  val Greater         = new Value("greater", ">")
  val LessOrEqual     = new Value("lessOrEqual", "<=") { override def someFlag = false }
  val Less            = new Value("less", "<")  
  final def apply(x: Int)( implicit dummy: DummyImplicit ): Value = super.apply(x).asInstanceOf[Value]
  final def withName(s: String)( implicit dummy: DummyImplicit ): Value = super.withName(s).asInstanceOf[Value]
}

You can check that you can now do things like ConditionOperator(2).toSql or ConditionOperator.withName("greaterOrEqual"), which both return ">=" as expected. Finally, the above gymnastic can be abstracted away:

abstract class CustomEnumeration extends Enumeration {
  type BaseValue = super.Val
  type CustomValue <: super.Value
  type Value = CustomValue
  final def apply(x: Int)( implicit dummy: DummyImplicit ): CustomValue = super.apply(x).asInstanceOf[CustomValue]
  final def withName(s: String)( implicit dummy: DummyImplicit ): CustomValue = super.withName(s).asInstanceOf[CustomValue]
}
object ConditionOperator extends CustomEnumeration {
  class CustomValue(name: String, val toSql: String) extends BaseValue(name) {
    def someFlag: Boolean = true
  }
  val Equal           = new Value("equal", "=")
  val NotEqual        = new Value("notEqual", "<>")
  val GreaterOrEqual  = new Value("greaterOrEqual", ">=")
  val Greater         = new Value("greater", ">")
  val LessOrEqual     = new Value("lessOrEqual", "<=") { override def someFlag = false }
  val Less            = new Value("less", "<")  
}
like image 30
Régis Jean-Gilles Avatar answered Dec 14 '22 10:12

Régis Jean-Gilles