Search code examples
scalaf#

What is Scala's counterpart of Discriminated Union in F#?


How would one go about transforming an Discriminated Union in F# to Scala:

type Expr =
    | Val     of String
    | Integer of Int32
    | Lower   of Expr * Expr
    | Greater of Expr * Expr
    | And     of Expr * Expr
    | Or      of Expr * Expr

There is a similar post talking about ADTs in F# and Scala, but that doesn't seem to be what I am after.


Solution

  • This is done with inheritance in scala (maybe unfortunately as it is more verbose)

    sealed trait Expr
    case class Val(s: String) extends Expr
    case class Integer(i: Int) extends Expr
    case class Lower(left: Expr, right: Expr) extends Expr
    case class Greater(left: Expr, right: Expr) extends Expr
    ...
    

    You could type further

    sealed trait Expr[A]
    case class Val(s: String) extends Expr[String]
    case class Integer(i: Int) extends Expr[Int]
    case class Lower[X](left: Expr[X], right: Expr[X])(implicit val ordering: Ordering[X]) extends Expr[Boolean]
    

    pattern matching with

    def valueOf[A](expr: Expr[A]) : A = expr match {
       case Val(s) => s
       case Integer(i) => i
       case l @ Lower(a,b) => l.ordering.lt(valueOf(a), valueOf(b))
       ...
    }
    

    valueOf would probably be better as a method in Expr

    sealed trait Expr[A] {def value: A}
    case class Val(value: String) extends Expr[String]
    case class Integer(value: Int) extends Expr[Int]
    case class Lower[X: Ordering](left: Expr[X], right: Expr[X]) extends Expr[Bool] {
       def value = implicitly[Ordering[X]].lt(left.value, right.value)
    }
    ...
    

    Some years later, as this still get some vote now and then.

    What is written above still works, but if you use Scala 3, it has introduced enum which is much more convenient.

    The declaration can now be

    enum Expr {
      case Val(s: String)
      case Integer(i: Int)
      case Lower(left: Expr, right: Expr)
    }
    

    or for the generic version (with GADT)

    enum Expr[A] {
      case Val(s: String) extends Expr[String]
      case Integer(i: Int) extends Expr[Int]
      case Lower[X: Ordering](
        left: Expr[X], 
        right: Expr[X]) extends Expr[Boolean]
    }
    

    Usage is the same as before, except that the various constructor are not in the top namespace anymore, so you must write e.g Expr.Val instead of just Val, both when creating one and when matching (it was actually customary in Scala 2 to write the cases a bit differently of what I had done to avoid having the cases in the top namespace too). However, a simple import Expr._ will make the names directly available again.

    Finally, a quite belated answer to @mcintyre321 's comment below: yes there is a warning when the match is not exhaustive.