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

3

28

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.

Gruel answered 14/9, 2011 at 7:49 Comment(0)
L
38

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.

Loach answered 14/9, 2011 at 7:56 Comment(2)
Really quick and really answered my question. Thank you!Gruel
Does this provide exhaustive matching (i.e. a compiler error if there are unhandled cases)Possing
K
2

I totally agree with Didier Dupont, but if you need higher level of abstraction, implementation of the option type in Scala gives a good intuition:

sealed trait Option[+E]

case class Some[+E]( element : E ) extends Option[E]
case object None extends Option[Nothing]

Source: https://mauricio.github.io/2013/12/25/learning-scala-by-building-scala-lists-part-3.html

Kamerman answered 17/6, 2016 at 4:38 Comment(0)
D
2

UPDATE for Scala 3:
Use the new enum syntax for more concise algebraic data types.

enum Expr:
  case Val(a: String)
  case Integer(a: Int)
  case Lower(a: Expr, b: Expr)
  case Greater(a: Expr, b: Expr)
  case And(a: Expr, b: Expr)
  case Or(a: Expr, b: Expr)
Debauch answered 2/11, 2023 at 18:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.