Blended Java/Scala Enums

517 views
Skip to first unread message

Michael Slinn

unread,
Sep 18, 2014, 10:10:27 AM9/18/14
to scala...@googlegroups.com
For the use case where a Java enum needs to be extended into Scala and a sealed implementation is desired, here is a working code example that could benefit from a macro, so the boilerplate is autogenerated. For the Java enum, I'm using an enum similar to Java 8's DayOfWeek:

public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY 
}


Here is the Scala code. The code works around type erasure by matching against the ordinal value:

object BlendedEnums extends App {
  import Day._

  trait EnumLike[T] {
    def name: String

    def compareTo(other: Day): Int

    override def equals(other: Object): Boolean = other.equals()

    def hashCode: Int

    def ordinal: Int

    def toString: String

    def values: Array[T]
  }

  sealed class CaseDay(val day: Day) extends EnumLike[CaseDay] {
    import CaseDay._

    lazy val name: String = day.name

    def compareTo(other: Day): Int = day.compareTo(other)

    override lazy val hashCode: Int = day.hashCode

    lazy val ordinal: Int = day.ordinal

    override lazy val toString = day.toString

    lazy val values: Array[CaseDay] = Array(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
  }

  object CaseDay {
    case object Monday extends CaseDay(MONDAY)
    case object Tuesday extends CaseDay(TUESDAY)
    case object Wednesday extends CaseDay(WEDNESDAY)
    case object Thursday extends CaseDay(THURSDAY)
    case object Friday extends CaseDay(FRIDAY)
    case object Saturday extends CaseDay(SATURDAY)
    case object Sunday extends CaseDay(SUNDAY)

    def valueOf(name: String) = Day.valueOf(name)
  }

  import CaseDay._
  def tellItLikeItIs(theDay: CaseDay): Unit = {
    val msg = theDay.ordinal match {
      case Monday.ordinal => "Mondays are bad."
      case Friday.ordinal => "Fridays are better."
      case Saturday.ordinal => "Weekends are best."
      case Sunday.ordinal => "Weekends are best."
      case _ => "Midweek days are so-so."
    }
    println(msg)
  }
  tellItLikeItIs(Monday)
  tellItLikeItIs(Tuesday)
  tellItLikeItIs(Wednesday)
  tellItLikeItIs(Sunday)
}

Comments?

Mike

EECOLOR

unread,
Sep 18, 2014, 3:10:51 PM9/18/14
to scala...@googlegroups.com
For your example this should be enough:

object BlendedEnums extends App {

  abstract class CaseEnum[T <: Enum[T]](val day: T) {
    lazy val name: String = day.name
    lazy val ordinal: Int = day.ordinal
  }

  sealed abstract class CaseDay(day:Day) extends CaseEnum(day) 
  
  object CaseDay {
    import Day._
    case object Monday extends CaseDay(MONDAY)
    case object Tuesday extends CaseDay(TUESDAY)
    case object Wednesday extends CaseDay(WEDNESDAY)
    case object Thursday extends CaseDay(THURSDAY)
    case object Friday extends CaseDay(FRIDAY)
    case object Saturday extends CaseDay(SATURDAY)
    case object Sunday extends CaseDay(SUNDAY)
    
    lazy val values = Seq(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
    
    def valueOf(name:String) = values.find(_.name == name)
  }

  import CaseDay._
  
  def tellItLikeItIs(theDay: CaseDay): Unit = {
    val msg = theDay match {
      case Monday => "Mondays are bad."
      case Friday => "Fridays are better."
      case Saturday => "Weekends are best."
      case Sunday => "Weekends are best."
      case _ => "Midweek days are so-so."
    }
    println(msg)
  }
  tellItLikeItIs(Monday)
  tellItLikeItIs(Tuesday)
  tellItLikeItIs(Wednesday)
  tellItLikeItIs(Sunday)
}

I am probably missing a use-case you had in mind. You can match directly on case objects, so there is no need for matching on the ordinal value.


Erik

--
You received this message because you are subscribed to the Google Groups "scala-sips" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-sips+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mike Slinn

unread,
Sep 18, 2014, 3:54:42 PM9/18/14
to scala...@googlegroups.com
Without the comparison of ordinals, output is:
Mondays are bad.
Mondays are bad.
Mondays are bad.
Mondays are bad.

With the comparison of ordinals, output is:
Mondays are bad.
Midweek days are so-so.
Midweek days are so-so.
Weekends are best.

The rest of the boilerplate is to mirror the functionality of Java's
enums. It is true that the demo program does not exercise each feature;
this tiny example was meant only to show the properties and methods that
would be generally useful when incorporated into a library.

Thank you,

Mike

EECOLOR

unread,
Sep 19, 2014, 2:35:45 AM9/19/14
to scala...@googlegroups.com
You are messing stuff up by writing an equals and hascode function. If you would have tried my example you would have noticed it working without ordinals.

About the other methods you created:
name - Present in my example
compareTo - Should by provided while implementing java.lang.Comparable
equals - Provided by `case` keyword
hashCode - Provided by `case` keyword
ordinal - Present in my example
toString - Provided by `case` keyword, you could theoretically override it
values - Does not make sense on an instance, that's why I put it on the companion object

So apart from the `compareTo` method and possibly `toString` behavior (which were not used in the example you provided) the simpler version I presented can be used. Note that if you are only using one enum, the CaseEnum class is not needed.


Erik




Mike

--
You received this message because you are subscribed to the Google Groups "scala-sips" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-sips+unsubscribe@googlegroups.com.

Michael Slinn

unread,
Sep 19, 2014, 6:24:02 AM9/19/14
to scala...@googlegroups.com
Erik,

Good catch re. equals and hashCode. I had not noticed you moved values to the companion object; that is good, and I made it an OrderedSetHere is a gist of the improved code example. Perhaps you might be able to improve it :)

Maybe I did not make myself clear in the initial posting, but the purpose of this code example was to show a generic way of extending multiple Java enums in a Scala program so Scala match statements worked, while preserving all of the functionality of Java enums for use by Scala programs. That is why I made the comment about how a macro could likely be used to generate the boilerplate, and why there are methods that are defined but not used. toString is intended to return the same value as the Java enum toString() method.

Thanks,

Mike

Michael Slinn

unread,
Sep 19, 2014, 6:59:23 AM9/19/14
to scala...@googlegroups.com
I just found this macro, which would be a step towards automating the boilerplate. Looks like the macro could be used to create values like this (but I have not tried it yet):

val values: SortedSet[CaseDayObject] = EnumerationMacros.sealedInstancesOf[CaseDayObject]

Simon Ochsenreither

unread,
Sep 19, 2014, 7:29:32 PM9/19/14
to scala...@googlegroups.com
For the use case where a Java enum needs to be extended into Scala and a sealed implementation is desired

Maybe I don't understand it ... why?

Michael Slinn

unread,
Sep 20, 2014, 9:32:17 AM9/20/14
to scala...@googlegroups.com
There is a lot of Java code in existence, and there is a need for Scala to interoperate with legacy Java code. It is important in a blended Java/Scala code base that there be only one definition of enums; it is undesirable to duplicate the definitions in two places because two languages are involved.

Java enums are much more powerful than simple collections of name/value pairs. Legacy Java code that takes advantage of Java enums' class-based approach should be able to be exposed as idiomatic Scala. To do this in a general manner requires Scala boilerplate to implement a pattern. It is this pattern that I am iterating towards with the code examples that I have shared. Since standard boilerplate adds no value to an API, it's visible surface area should be minimized, which is why it should be automatically generated.

Simon Ochsenreither

unread,
Sep 20, 2014, 9:54:59 AM9/20/14
to scala...@googlegroups.com
Mhh, I think I have a hard time seeing what generating all those case objects enables us to do over just using plain enums.

Michael Slinn

unread,
Sep 20, 2014, 11:15:16 AM9/20/14
to scala...@googlegroups.com
Simon, 

If you are referring scala.Enumeration subclasses, the reasons against using them are described here.

Typical Scala programs have thousands of class instances at runtime (once you account for dependencies). Small Play Framework apps have tens of thousands of classes instances at runtime. The extra overhead of adding a few singletons to implement enum case objects is negligible.

Thanks,

Mike

Simon Ochsenreither

unread,
Sep 20, 2014, 4:25:18 PM9/20/14
to scala...@googlegroups.com
No, not scala.Enumeration (why would I ever use that class?).

Just plain Java enums, either defined via javac or as @enum in Scala.

Michael Slinn

unread,
Sep 20, 2014, 4:32:41 PM9/20/14
to scala...@googlegroups.com
Simon,

Have I answered your question?

Thanks,

Mike

Simon Ochsenreither

unread,
Sep 20, 2014, 5:00:58 PM9/20/14
to scala...@googlegroups.com
No, I'm not getting it.

As far as I understand, you have Java enums and the issue with them is ...?

Michael Slinn

unread,
Sep 20, 2014, 5:09:44 PM9/20/14
to scala...@googlegroups.com
Simon,

In the initial posting of this thread I stated that Java enums do not allow for a sealed implementation in Scala code. By that I meant that verification of exhaustive matches is not supported.

Mike

Simon Ochsenreither

unread,
Sep 21, 2014, 5:45:14 AM9/21/14
to scala...@googlegroups.com
Ok, now I'm really confused:

import javax.swing.DropMode
def whichDropMode(dm: DropMode) = dm match {
  case DropMode.INSERT => "Insert"
}

<console>:9: warning: match may not be exhaustive.
It would fail on the following inputs: INSERT_COLS, INSERT_ROWS, ON, ON_OR_INSERT, ON_OR_INSERT_COLS, ON_OR_INSERT_ROWS, USE_SELECTION
       def whichDropMode(dm: DropMode) = dm match {
                                         ^


Michael Slinn

unread,
Sep 21, 2014, 12:21:20 PM9/21/14
to scala...@googlegroups.com
Simon,

Please provide a link to the rest of the code.

Thanks,

Mike

√iktor Ҡlang

unread,
Sep 21, 2014, 12:30:49 PM9/21/14
to <scala-sips@googlegroups.com>
╭─[18:26:18] viktorklang@tengil/~
╰─○ scala
Welcome to Scala version 2.11.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import javax.swing.DropMode
import javax.swing.DropMode

scala> def whichDropMode(dm: DropMode) = dm match {
     |   case DropMode.INSERT => "Insert"
     | }
<console>:8: warning: match may not be exhaustive.

It would fail on the following inputs: INSERT_COLS, INSERT_ROWS, ON, ON_OR_INSERT, ON_OR_INSERT_COLS, ON_OR_INSERT_ROWS, USE_SELECTION
       def whichDropMode(dm: DropMode) = dm match {
                                         ^
whichDropMode: (dm: javax.swing.DropMode)String

scala>

--
You received this message because you are subscribed to the Google Groups "scala-sips" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-sips+...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Cheers,

Michael Slinn

unread,
Sep 21, 2014, 12:59:20 PM9/21/14
to scala...@googlegroups.com
Seems Scala compilers work differently in this regard since Scala 2.10.x,however I do not see any mention of this in the scalac changelog.


2.9.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_67).

Type in expressions to have them evaluated.
Type :help for more information.

scala> import javax.swing.DropMode
import javax.swing.DropMode

scala> def whichDropMode(dm: DropMode) = dm match {
     |   case DropMode.INSERT => "Insert"
     | }
whichDropMode: (dm: javax.swing.DropMode)java.lang.String

scala>


Welcome to Scala version 2.10.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_67).

Type in expressions to have them evaluated.
Type :help for more information.

scala>  import javax.swing.DropMode
import javax.swing.DropMode

scala> def whichDropMode(dm: DropMode) = dm match {
     |   case DropMode.INSERT => "Insert"
     | }
<console>:8: warning: match may not be exhaustive.
It would fail on the following inputs: INSERT_COLS, INSERT_ROWS, ON, ON_OR_INSERT, ON_OR_INSERT_COLS, ON_OR_INSERT_ROWS, USE_SELECTION
       def whichDropMode(dm: DropMode) = dm match {
                                         ^
whichDropMode: (dm: javax.swing.DropMode)String


However, there remains the inability to operate on enum values as members of a set in an Scala idiomatic way - unless someone knows of a technique?

Thanks,

Mike

Simon Ochsenreither

unread,
Sep 21, 2014, 1:04:45 PM9/21/14
to scala...@googlegroups.com
At the type or the value level?

Value level: DropMode.values.toSet

√iktor Ҡlang

unread,
Sep 21, 2014, 1:13:52 PM9/21/14
to <scala-sips@googlegroups.com>
On Sun, Sep 21, 2014 at 1:04 PM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
At the type or the value level?

Value level: DropMode.values.toSet

Thinking about it you could even make a value-class set that wraps over any Java enum type. (So you don't need to build a new set for each DropMode.values.toSet invocation.)
 

--
You received this message because you are subscribed to the Google Groups "scala-sips" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-sips+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Cheers,

Nils Kilden-Pedersen

unread,
Sep 21, 2014, 2:14:44 PM9/21/14
to scala...@googlegroups.com


scala> import collection.JavaConverters._
import collection.JavaConverters._

scala> java.util.EnumSet.allOf(classOf[DropMode]).asScala
res3: scala.collection.mutable.Set[javax.swing.DropMode] = Set(USE_SELECTION, ON, INSERT, INSERT_ROWS, INSERT_COLS, ON_OR_INSERT, ON_OR_INSERT_ROWS, ON_OR_INSERT_COLS)

Michael Slinn

unread,
Sep 21, 2014, 2:18:07 PM9/21/14
to scala...@googlegroups.com
Here is some behavior that would be good to be able automatically apply to Java enums:

scala> val values: collection.immutable.SortedSet[Day] = {
     |   val ordering = Ordering.by { day: Day => day.ordinal }
     |   collection.immutable.TreeSet(Day.values:_*)(ordering)
     | }
values: scala.collection.immutable.SortedSet[Day] = TreeSet(SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY)

scala> implicit class EnrichedDay(day: Day) {
     |   def <(that: Day): Boolean = day.ordinal < that.ordinal
     |   def <=(that: Day): Boolean = day.ordinal <= that.ordinal
     |   def >(that: Day): Boolean = day.ordinal > that.ordinal
     |   def >=(that: Day): Boolean = day.ordinal >= that.ordinal
     | }
defined class EnrichedDay

scala> Day.SUNDAY < Day.MONDAY
res8: Boolean = true

Tom Switzer

unread,
Sep 21, 2014, 2:25:59 PM9/21/14
to scala...@googlegroups.com

This should already exist, since, iirc, Java enums imement Comparable.

import scala.math.Ordering. Implicits

Days.MONDAY < Days.FRIDAY

Tom Switzer

unread,
Sep 21, 2014, 2:27:10 PM9/21/14
to scala...@googlegroups.com

Oops, that should be import scala.math.Ordering.Implicits._

√iktor Ҡlang

unread,
Sep 21, 2014, 2:28:39 PM9/21/14
to <scala-sips@googlegroups.com>
Also, you don't want a TreeSet, you want a bit set using ordinal to add and remove.
--
Cheers,

Michael Slinn

unread,
Sep 21, 2014, 2:30:13 PM9/21/14
to scala...@googlegroups.com
Tom,

scala> import scala.math.Ordering. Implicits
import scala.math.Ordering.Implicits

scala> Day.MONDAY < Day.FRIDAY
<console>:9: error: value < is not a member of Day
              Day.MONDAY < Day.FRIDAY

Mike :(

Michael Slinn

unread,
Sep 21, 2014, 2:32:45 PM9/21/14
to scala...@googlegroups.com
scala> import scala.math.Ordering. Implicits._
import scala.math.Ordering.Implicits._

scala> Day.MONDAY < Day.FRIDAY
res3: Boolean = true

:)

Michael Slinn

unread,
Sep 21, 2014, 2:35:49 PM9/21/14
to scala...@googlegroups.com

Michael Slinn

unread,
Sep 21, 2014, 2:41:20 PM9/21/14
to scala...@googlegroups.com
Victor,

BitSets require Int values, I think it is better to work with Day values.

Mike

√iktor Ҡlang

unread,
Sep 21, 2014, 5:22:51 PM9/21/14
to <scala-sips@googlegroups.com>
Since you have a bijection between Day values and Int values BitSet is much more compact.

--
You received this message because you are subscribed to the Google Groups "scala-sips" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-sips+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Cheers,
Reply all
Reply to author
Forward
0 new messages