8

我定义了以下有区别的联合:

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,但我不想用这种会随着时间不断增长的特殊方法污染我的初始类型声明。因此,我决定使用一种抽象方法,我可以在文件中进一步使用内部类型扩展来实现该方法。这是我所做的:

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

虽然现在sprintf和两者都输出相同的字符串,但如果我想要的话ToString,没有办法获得Add (Con 2,Con 3)输出。(2 + 3)

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

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

4

3 回答 3

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.
于 2013-08-03T22:28:45.857 回答
6

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

相反,使用 stringify 的静态成员定义一个类型(我们需要虚拟类型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)
于 2013-08-03T22:20:21.513 回答
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))

此答案的引用:

在复杂的程序中,清晰的类型签名确实更容易维护可组合性。

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

于 2013-08-04T13:51:50.303 回答