首页 文章

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

提问于
浏览
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 的成员,但我不想用这种随着时间的推移而不断增长的专门方法污染我的初始类型声明 . 因此,我决定使用一个抽象方法,我可以在文件中进一步向下实现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 结束了"cheating":

[<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) .

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

附:我还注意到,如果我将 StructuredFormatDisplay 属性放在扩充而不是原始类型上,它对我来说似乎没有问题 . 似乎F#编译器应该将属性添加到类型定义中,或者禁用类型augmentations上的属性 .

3 回答

  • 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也更容易 .

  • 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.
    
  • 6

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

    相反,定义一个带有字符串化的静态成员的类型(我们需要虚拟类型,因为 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)
    

相关问题