Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ADTs in F# and Scala [closed]

What are the key differences between ADTs in F# and Scala? Is there anything that F#'s ADTs can do but Scala's ADTs cannot (and vice versa)?

like image 871
anonymous Avatar asked Dec 17 '22 00:12

anonymous


1 Answers

Conceptually, I think that both languages provide the same power - in F# you can declare ADTs using discriminated unions and in Scala, you can use case classes. The declaration in Scala using classes may get a little bit longer than the F# version (as pointed out by Yin Zhu), but then you can use pattern matching with similar elegancy in both of the languages.

Here is an example (from this article) of simplifying terms:

def simplify(term: Term) = term match {
  case Mul(Num(0), x) => Num(0)
  case Mul(Num(1), x) => x
  case _ => term
}

The same code in F# using match would look very similar:

let simplify term = 
  match term with 
  | Mul(Num(0), x) -> Num(0)
  | Mul(Num(1), x) -> x
  | _ -> term

Differences I think there are a few differences when it comes to more advanced (related) features.

  • In Scala, each case is also a type, so you can for example define a method that takes Num or Mul as an argument. In F#, this is not possible, because Num and Mul are just constructors of type Term. I suppose this may be sometimes useful, but most of the time, you'll work with values of type Term anyway.

  • Related to the previous point - in Scala, you can also define methods for individual cases. You can for example define a method in the Num class. In F#, all members have to be members of the Term type.

  • In F#, you can use active patterns to hide the internal representation of the type (e.g. when exporting it from a module). This is very useful for library design. For example, you can define active patterns:

    val (|Mul|_|) // return Some(..) if Term represents multiplication
    val (|Num|_|) // return Some(..) if Term represents number
    

    The internal representation can change over time without affecting the library interface, so you can for example implement the interface like this:

    type Term = Binary of string * Term * Term | Num of int
    let (|Num|_|) = function Num n -> Some n | _ -> None 
    let (|Mul|_|) = function Binary("*", a, b) -> Some(a, b) | _ -> None
    
like image 129
Tomas Petricek Avatar answered Jan 04 '23 23:01

Tomas Petricek