F#中区分联合的类型扩展

时间:2013-08-03 18:59:59

标签: f# discriminated-union type-extension

我定义了以下歧视联盟:

type Expr = 
    | Con of Num
    | Var of Name
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mult of Expr * Expr
    | Div of Expr * Expr
    | Pow of Expr * Expr

然后我创建了一个漂亮的打印功能,如下所示:

let rec stringify expr =
    match expr with
    | Con(x) -> string x
    | Var(x) -> string x
    | Add(x, y) -> sprintf "(%s + %s)" (stringify x) (stringify y)
    | Sub(x, y) -> sprintf "(%s - %s)" (stringify x) (stringify y)
    | Mult(x, y) -> sprintf "(%s * %s)" (stringify x) (stringify y)
    | Div(x, y) -> sprintf "(%s / %s)" (stringify x) (stringify y)
    | Pow(x, y) -> sprintf "(%s ** %s)" (stringify x) (stringify y)

现在我想让我的Expr类型将此函数用于其ToString()方法。例如:

type Expr = 
    | Con of Num
    | Var of Name
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mult of Expr * Expr
    | Div of Expr * Expr
    | Pow of Expr * Expr
    override this.ToString() = stringify this

但我不能这样做,因为stringify尚未定义。答案是将Stringify定义为Expr的成员,但我不想用这种随着时间的推移而不断增长的专门方法污染我的初始类型声明。因此,我决定使用一种抽象方法,我可以在文件中进一步向下intrinsic type extension。这是我做的:

type Expr = 
    | Con of Num
    | Var of Name
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mult of Expr * Expr
    | Div of Expr * Expr
    | Pow of Expr * Expr
    override this.ToString() = this.Stringify()
    abstract member Stringify : unit -> string

但是我得到以下编译器错误:

  

错误FS0912:不允许使用此声明元素   增强

该消息甚至看起来都不正确(我还没有创建类型扩充),但我明白为什么它在抱怨。它不希望我在有区别的联合类型上创建一个抽象成员,因为它不能被继承。即使我真的不想继承,我希望它在C#中表现得像一个部分类,我可以在其他地方完成定义(在这种情况下是相同的文件)。

我最后使用StructuredFormatDisplay属性的后期绑定功能以及sprintf来“欺骗”:

[<StructuredFormatDisplay("{DisplayValue}")>]
type Expr = 
    | Con of Num
    | Var of Name
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mult of Expr * Expr
    | Div of Expr * Expr
    | Pow of Expr * Expr
    override this.ToString() = sprintf "%A" this

/* stringify function goes here */

type Expr with
    member public this.DisplayValue = stringify this

虽然现在sprintfToString都输出相同的字符串,但如果我需要,则无法获得Add (Con 2,Con 3)输出而不是(2 + 3)。< / p>

那么还有其他办法可以做我想做的事情吗?

P.S。我还注意到,如果我将StructuredFormatDisplay属性放在扩充而不是原始类型上,它就不起作用。这种行为对我来说似乎不正确。似乎F#编译器应该将属性添加到类型定义中,或者禁用类型augmentations上的属性。

3 个答案:

答案 0 :(得分:8)

您是否考虑在扩充中定义ToString

type Num = int
type Name = string

type Expr = 
    | Con of Num
    | Var of Name
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mult of Expr * Expr
    | Div of Expr * Expr
    | Pow of Expr * Expr

let rec stringify expr =
    match expr with
    | Con(x) -> string x
    | Var(x) -> string x
    | Add(x, y) -> sprintf "(%s + %s)" (stringify x) (stringify y)
    | Sub(x, y) -> sprintf "(%s - %s)" (stringify x) (stringify y)
    | Mult(x, y) -> sprintf "(%s * %s)" (stringify x) (stringify y)
    | Div(x, y) -> sprintf "(%s / %s)" (stringify x) (stringify y)
    | Pow(x, y) -> sprintf "(%s ** %s)" (stringify x) (stringify y)

type Expr with
    override this.ToString() = stringify this

然而, 具有

的难看的副作用
warning FS0060: Override implementations in augmentations are now deprecated. Override implementations should be given as part of the initial declaration of a type.

答案 1 :(得分:6)

一个甚至不需要类型扩展的解决方案怎么样。

相反,定义一个带有staticify的静态成员的类型(我们需要虚拟类型为type a ... and b,需要b作为类型

type Num = string //missing
type Name = string //missing
type Expr = 
    | Con of Num
    | Var of Name
    | Add of Expr * Expr
    | Sub of Expr * Expr
    | Mult of Expr * Expr
    | Div of Expr * Expr
    | Pow of Expr * Expr
    override this.ToString() = type_dummy.stringify this
and type_dummy = 
    static member stringify expr =
        let stringify = type_dummy.stringify
        match expr with
        | Con(x) -> string x
        | Var(x) -> string x
        | Add(x, y) -> sprintf "(%s + %s)" (stringify x) (stringify y)
        | Sub(x, y) -> sprintf "(%s - %s)" (stringify x) (stringify y)
        | Mult(x, y) -> sprintf "(%s * %s)" (stringify x) (stringify y)
        | Div(x, y) -> sprintf "(%s / %s)" (stringify x) (stringify y)
        | Pow(x, y) -> sprintf "(%s ** %s)" (stringify x) (stringify y)

答案 2 :(得分:6)

事实上,stringify 必须与数据类型一起增长,否则最终会出现不完整的模式匹配。对数据类型进行任何必要的修改都需要修改stringify。作为个人意见,我会考虑将两者放在同一个地方,除非项目非常复杂。

但是,由于您更喜欢清楚DU类型,请考虑将数据类型包装到单个案例DU中:

// precede this with your definitions of Expr and stringify
type ExprWrapper = InnerExpr of Expr with
    static member Make (x: Expr) = InnerExpr x
    override this.ToString() = match this with | InnerExpr x -> stringify x

// usage
let x01 = Add(Con 5, Con 42) |> ExprWrapper.Make
printfn "%O" x01
// outputs: (5 + 42)
printfn "%s" (x01.ToString())
// outputs: (5 + 42)
printfn "%A" x01
// outputs: InnerExpr(Add (Con 5,Con 42))

来自this answer的引文:

  

在复杂程序中,清晰类型签名确实可以更容易地保持可组合性。

     

不仅可以更简单地向单个案例的DU添加更多案例,而且使用成员和静态方法扩展DU也更容易。