Search code examples
scalascala-3

How to use an enum value in a previous ordinal of the same enum?


(FIG 1) I have the following enum:

enum Paint(val components: Array[Paint]):
  case Red    extends Paint(Array())
  case Orange extends Paint(Array(Red, Yellow))
  case Yellow extends Paint(Array())
  case Green  extends Paint(Array(Yellow, Blue))
  case Blue   extends Paint(Array())
  case Purple extends Paint(Array(Red, Blue))
  def isPrimary: Boolean = components.isEmpty

(FIG 2) When I run the following code:

@main def helloInteractive(): Unit =
  for
    p <- Paint.values
  do
    println(p.components.mkString(s"${p} is made from ", ", ", ""))

(FIG 3) The output I get is:

Red is made from 
Orange is made from Red, null
Yellow is made from 
Green is made from Yellow, null
Blue is made from 
Purple is made from Red, Blue

(FIG 4) But if I change my enum to be:

enum Paint(val components: Array[Paint]):
  case Red extends Paint(Array())
  case Yellow extends Paint(Array())
  case Blue extends Paint(Array())
  case Orange extends Paint(Array(Red, Yellow))
  case Green extends Paint(Array(Yellow, Blue))
  case Purple extends Paint(Array(Red, Blue))
  def isPrimary: Boolean = components.isEmpty

(FIG 5) I get the correct values in components:

Red is made from 
Yellow is made from 
Blue is made from 
Orange is made from Red, Yellow
Green is made from Yellow, Blue
Purple is made from Red, Blue

I figure that the enum is being created in sequence, therefore something further down the file does not exist yet. Is that correct?

My questions are:

  1. How do I get the FIG 5 values while maintaining the enum ordinal from FIG 1?
    I do not want to sort this, I want the enum.ordinal to be as FIG 1.
  2. Is this simply a bad idea, and if "yes", why?

This code is me learning the language, it's toy code, don't worry about it being silly or pointless.


Solution

    1. The most strightforward and warningless way of having the correct output while maintaining the ordinals is to make the enum parameter called by-name. It is also possible to change its type to LazyList, though the current implementation of the latter will generate a lot of warnings if safe initialization check (-Ysafe-init) is used:
        enum Paint(comps: => Array[Paint]):
          case Red    extends Paint(Array())
          case Orange extends Paint(Array(Red, Yellow))
          case Yellow extends Paint(Array())
          case Green  extends Paint(Array(Yellow, Blue))
          case Blue   extends Paint(Array())
          case Purple extends Paint(Array(Red, Blue))
          def isPrimary: Boolean = components.isEmpty
          lazy val components = comps
    
        import scala.collection.immutable.LazyList.empty
        enum Paint(val components: LazyList[Paint]):
          case Red    extends Paint(empty)
          case Orange extends Paint(Red #:: Yellow #:: empty)
          case Yellow extends Paint(LazyList.empty)
          case Green  extends Paint(Yellow #:: Blue #:: empty)
          case Blue   extends Paint(empty)
          case Purple extends Paint(Red #:: Blue #:: empty)
          def isPrimary: Boolean = components.isEmpty
    
    1. Your use case seems legit.