Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant way to implement enum using case object

Tags:

enums

scala

I am trying to simulate enum behavior using case object. It feels a little verbose, and not elegant, and I was wondering if there is a better way to achieve this.

So here is an example:

sealed trait Operator
object Operator{
  def apply(value: String) = value match {
    case EqualsOp.name => EqualsOp
    case NotEqualOp.name => NotEqualOp
    case ContainsOp.name => ContainsOp
    case NotContainsOp.name => NotContainsOp
    case _ => UnknownOp
  }
}

case object EqualsOp extends Operator { val name = "equals" }
case object NotEqualOp extends Operator { val name = "not_equals" }
case object ContainsOp extends Operator { val name = "contains" }
case object NotContainsOp extends Operator { val name = "not_contains" }

Is there a better way to get this reverse mapping from a string to the actual case object? Or in general better implement this?

like image 741
Tomer Avatar asked Mar 03 '16 15:03

Tomer


2 Answers

I prefer such approach:

sealed case class ProgressStatus(value: String)

object ProgressStatus {
  object IN_PROGRESS extends ProgressStatus("IN_PROGRESS")
  object ACCEPTED extends ProgressStatus("ACCEPTED")
  object REJECTED extends ProgressStatus("REJECTED")

  val values = Seq(IN_PROGRESS, ACCEPTED, REJECTED)
}

to get a value:

ProgressStatus.IN_PROGRESS.value

to get all values:

ProgressStatus.values
like image 111
Mariusz Beltowski Avatar answered Nov 17 '22 06:11

Mariusz Beltowski


Basic enumerations in Scala are clumsy:

  1. If you want to use them in pattern-matching, you will not see the next warning by compiler "match may not be exhaustive" and you can unexpectedly face with scala.MatchError in runtime.
  2. They are not compatible with Java’s enum - it's not very scary if you don't support API for Java, but if you do it, it can be an unexpected disappointment for you.
  3. Overloading with Scala's enumerations not working in due to the fact of the same type of enumerations after erasure. So the next code snapshot is not valid:

    object WeekDays extends Enumeration {
      val Mon, Tue, Wed, Thu, Fri = Value
    }
    
    object WeekEnds extends Enumeration {
      val Sat, Sun = Value
    }
    
    object DaysOperations {
      def f(x: WeekEnds.Value)  = "That's a weekend"
      def f(x: WeekDays.Value) = "That's a weekday"
    }
    

It will throw error: double definition: have the same type after erasure: (x: Enumeration#Value)String. As you see, scala.Enumeration is not user-friendly, prefer don't use it, it will make your life easier.

Correct solution: The right approach is using the combination of case object or object with sealed class:

object WeekDays {
  sealed trait EnumVal
  case object Mon extends EnumVal
  case object Tue extends EnumVal
  case object Wed extends EnumVal
  case object Thu extends EnumVal
  case object Fri extends EnumVal
  val daysOfWeek = Seq(Mon, Tue, Wed, Thu, Fri)
}

Also, you can use wrapper objects like enums:

sealed trait Day { def description: String }
case object Monday extends Day { val description = "monday is awful" }

Leveraging third-party library - Enumeratum also can solve problems of scala.enumeration. It's a type-safe and easy-to-use implementation:

    libraryDependencies ++= Seq(
    "com.beachape" %% "enumeratum" % enumeratumVersion
    )

    import enumeratum._

    sealed trait Day extends EnumEntry

    object Greeting extends Enum[Greeting] {
      val values = findValues

      case object Mon      extends Day 
      case object Tue      extends Day 
      case object Wed      extends Day 
      case object Thu      extends Day 
      case object Fri      extends Day 
    }
like image 22
Artem Avatar answered Nov 17 '22 07:11

Artem