Defining methods for a member of a Scala3 enum?

Is it possible to define a new method for a member of an enum in Scala3? (Not every member - just one member.)

I tried

enum E:                                                                                                                                                                                               
  case B extends E() 
    def b() = "b"

And killed the REPL with

casException in thread "main" java.lang.AssertionError: assertion failed: 
dotty.tools.dotc.parsing.Scanners$$anon$1@1fe8f5e8
at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
at dotty.tools.dotc.parsing.Scanners$Scanner.insert(Scanners.scala:360)
at dotty.tools.dotc.parsing.Scanners$Scanner.observeOutdented(Scanners.scala:586)
at dotty.tools.dotc.parsing.Parsers$Parser.acceptStatSepUnlessAtEnd(Parsers.scala:327)

Thanks,

David

This code compiles and works fine for me - both in compiler and REPL with local 3.0.2, and in Scastie with 3.0.2, 3.0.1 and 3.0.0.

While this code compiles, it doesn’t define a member just for B despite the indentation, it defines a member in E. There is intentionally no way to define a member for just a case of an enum, since the case type usually gets widened to the enum type.

4 Likes

Ouch! Stepped right into the trap. :confused: Interesting, thanks!

Why is that intentional?

I’ve found it really useful to have a list of case objects that all implement a sealed trait in Scala 2. I give case objects their own methods as needed. I was hoping to replace that pattern with Scala 3 enums - to be less pattern-y, and to have less to explain to the kids.

I didn’t expect to find a Scala 3 puzzler this early. Thanks!

Well you can still do that.

enum was specifically design for cases were you don’t want the leafs of your ADT to have their own types.

Yes. Why? (Under the hood I think they do have their own def ordinal:Int )

Sorry, I don’t understand your reply.

Thanks. There may be something that seems self-evident to you that is opaque to me.

Why were enums specifically design for cases were you don’t want the leafs of your ADT to have their own types ? Why limit them?

Uhm, not sure how to answer that.
Is like asking why 1 + 2 returns the sum of the two numbers instead of the multiplication.
Is just what the designers chose.

Maybe I may say that such is the most common case; at least in my experience.

I think the short answer is “Because that’s how Java’s Enums work”, and the new Enum in Scala is designed to be compatible with Java’s.

1 Like

Thanks. Pragmatics make sense.

To extend the arithmetic example, what I’d hoped for was something that would let me have an enumeration of operations. Add, Subtract, and Multiply are all pretty simple, with the same shape. Divide could implement all the odd code that expresses what a mess division is. With a Java-style enum the special mess of Divide has to be held elsewhere.

For my case: I’ve got a set of sensor modes that work with a single sensor. The modes have specific methods to interpret the same raw signal. I’d like to use an enum because it’s easier to explain than that pattern.

Can an enum take a function as a parameter? I have to explain higher-order functions already. That could be interesting. (That still wouldn’t give an extra named method to the enum.)

Can you show more or less what you are trying to model and why it is important for each enum case to have different methods?

Thanks. Here’s an example of the core idea:

enum Operator:

  case Add extends Operator:
    def apply(a:Int, b:Int):Int = a + b

  case Subtract extends Operator:
    def apply(a:Int, b:Int):Int = a - b

  case Multiply extends Operator:
    def apply(a:Int, b:Int):Int = a * b

  case Divide extends Operator:
    def floor(a:Int, b:Int):Rational =
      if (b == 0) throw NeverDoThisException()
      a/b

    def fraction(a:Int, b:Int):Rational =
      if (b == 0) throw NeverDoThisException()
      Rational(a,b)

    def withRemainder(a:Int, b:Int):(Int,Int) =
      if (b == 0) throw NeverDoThisException()
      (a/b, a%b)

The real-world example is that I’m modeling sensors in ev3dev. Appendix A: Sensor Data — ev3dev-stretch Linux kernel drivers 19 documentation

Each physical sensor plugged into the brick shows up as a directory in /sys/class/lego-sensor . I use an outer class to represent each sensor on the robot - Ev3ColorSensor . Every time the sensor is (unplugged and) plugged back in then a new directory appears. I use a new object to read and write to that new directory for every plug-in. Every kind of sensor has multiple modes; the mode determines how the files in the directory should be interpreted. There are several sensors of interest, with several modes each. Modeling them as case objects or case classes that extend a common trait - using the pattern for enums I used in scala 2 - feels like a lot of boilerplate.

Modes for the Ev3ColorSensor are ReflectedColor, AmbientColor, DefinedColor (one of eight colors), RawReference, RawRgb, and Calibrate . I’d like to represent the modes as an enum - because the modes are a bound set of immutable things. I want the students to be able to only use methods for each mode that make sense: I’d like for a red():Int, green():Int, and blue():Int method to only exist on RawRgb, and a color():Color method to return one of the 8 colors only to exist in DefinedColor mode.

I want to introduce enums early, and inheritance later. But I’d have to do a feature request to get that in Scala 3. And that’d carry Scala past the Java enum limitations. I think that’s the boundary in Scala3.

1 Like

In your example, I really don’t see the value of the abstraction at all; what is the point of having Operator and it being sealed?
It is not an ADT since it is not holding data, and is not a traditional OOP interface because the root type doesn’t provide anything of value at all.

My general advice would be to separate data from logic.
So the idea would be that the enum cases are just plain objects / classes, and there is an interpreter which does things according to the data inside the ADT. That way you don’t need to have special methods inside each case. - Or, just go the traditional OOP route of having an open interface.

1 Like