Equivalent of Scala “case class” in F#

2020-05-22 06:22发布

问题:

I am looking for the equivalent in F# of "case classes" that are available in Scala.

Cases classes are very useful when you want to create custom classes with methods and fields and still be able to use them with pattern matching, as described in this article of the Scala website.

Does anyone know if the same exists in F#?

回答1:

As Brian mentions, there are two ways for pattern matching: 1. Discriminated unions and 2. active pattern on an existing type.

Let's start from this Scala example:

abstract class Term
case class Var(name: String) extends Term
case class Fun(arg: String, body: Term) extends Term
case class App(f: Term, v: Term) extends Term

This OO design could be translated to discriminated unions (DU) in F#:

type Term = 
    Var of string 
    | Fun of string * Term 
    | App of Term * Term

Base on this DU, you can matching a Term value to find what subtype it is:

let eval (t: Term) = 
    match t with
    | Var (name) -> ...
    | Fun (para, body) -> ...
    | App (t1, t2) -> ...

Notice that you can have methods and properties defined on this Term type:

type Term = 
    Var of string 
    | Fun of string * Term 
    | App of Term * Term
    with 
    member x.Type() = 
        match x with
        | Var _ -> 0
        | Fun _ -> 1
        | App _ -> 2

Now here comes the differences:

  1. you cannot define methods on its subtypes: Var, Fun, and App.

  2. the methods you can define on Term are immutable.

  3. it is not possible to extend a DU once it is defined. Think about you now need to add a For subtype to Term. Then you have to change a lot of code where a Term is pattern matched.

  4. while in oo design, it is less a problem. because the new subtype could carry its own implementations.

In F#, DU should be first considered when you want to build succinct type matching over subtypes. But it also has obvious restrictions. I think activity pattern matching is more equal to the case class in Scala (I only read a little Scala):

// define the classes for different term types
[<AbstractClass>]
type Term() = 
    abstract Value: int with get

type Var(name:string) =
    inherit Term()
    override x.Value = 
        0
    member x.Name with get() = name

type Fun(name:string, body:Term) = 
    inherit Term()
    override x.Value = 
        0
    member x.Name with get() = name
    member x.Body with get() = body


type App(t1:Term, t2:Term) = 
    inherit Term()
    override x.Value = 
        0    
    member x.Term1 with get() = t1
    member x.Term2 with get() = t2

// the pattern function 
let (|TVar|TFun|TApp|) (x:Term) = 
    match x with
    | :? Var -> 
        let y = x :?> Var
        TVar(y.Name)
    | :? Fun -> 
        let y = x :?> Fun
        TFun(y.Name, y.Body)
    | :? App ->
        let y = x :?> App
        TApp(y.Term1, y.Term2)

and the eval function using active pattern:

let eval2 (t:Term) = 
    match t with
    | TVar (name) -> 0
    | TFun (name, body) -> 0
    | TApp (t1, t2) -> 0

Activity patten combines the good things on both sides: functional programming and object oriented.

ref. here and here for activity patterns.

You can further refer to the original paper on active pattern by Don Syme.



回答2:

Discriminated unions? You can add member methods to them. Alternatively you can use active patterns on an existing class.