F#中类似于Scala的“case class”的等效方式

20

我正在寻找F#中与Scala中的"case classes"相当的东西。

当您想要创建具有方法和字段的自定义类并仍然能够使用它们进行模式匹配时,"case classes"非常有用,正如Scala网站上的article所描述的那样。

是否有人知道F#中是否存在相同的功能?

2个回答

26

正如Brian所提到的,有两种方式进行模式匹配:1. 通过判别联合(Discriminated unions);2. 在现有类型上使用活动模式(active pattern)。

我们从这个Scala示例开始:

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

这个面向对象的设计可以翻译成F#中的区分联合类型(discriminated unions, DU):

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

基于这个 DU,你可以匹配一个 Term 值以找到它是哪种子类型:
let eval (t: Term) = 
    match t with
    | Var (name) -> ...
    | Fun (para, body) -> ...
    | App (t1, t2) -> ...

请注意,您可以在此Term类型上定义方法和属性:

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

现在有一些不同之处:
  1. 您不能在其子类型(VarFunApp)上定义方法。

  2. Term上定义的方法是不可变的。

  3. 一旦定义了DU,就无法扩展它。想象一下,现在您需要向Term添加一个For子类型。然后,您必须更改许多代码,其中匹配模式为Term

  4. 而在oo设计中,则不太成问题。因为新的子类型可以携带自己的实现。

在F#中,当您想要构建对子类型的简洁类型匹配时,应首先考虑DU。但它也有明显的限制。我认为活动模式匹配更等同于Scala中的case类(我只读过一点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)

使用活动模式的eval函数:

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

活动模式结合了函数式编程和面向对象编程的优点。

参考:这里这里 了解活动模式。

您还可以参考 Don Syme 的原始论文关于活动模式的介绍


1
非常好的答案!谢谢。我仍然认为在Scala中更容易,我想知道F#以后是否会包含这个功能。 - SRKX

7

区分联合?您可以向它们添加成员方法。或者,您可以在现有类上使用活动模式。


我尝试在MSDN上查找如何向带标签的联合体添加成员方法,但是我找不到。你能给一个示例吗? - SRKX
@JSmaga,请查看我关于向DU类型添加方法的答案。 - Yin Zhu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接