2013-08-03 52 views
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 

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

error FS0912: This declaration element is not permitted in an augmentation

的消息并不甚至似乎正确的(我不是创造一个增强型还),但我明白为什么它在抱怨。它不希望我在受歧视的联合类型上创建抽象成员,因为它不能被继承。尽管我不想继承,但我希望它在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.S.我还注意到,如果我将StructuredFormatDisplay属性放在扩充上而不是原始类型上,它不起作用。这种行为对我来说似乎不正确。似乎F#编译器应该将该属性添加到类型定义中或禁用类型增量上的属性。

回答

5

事实上丑陋的副作用,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

In complex programs clear type signatures indeed make it easier to maintain composability.

Not only it's simpler to add more cases to single-case DUs, but also it's easier to extend DUs with member and static methods.

+1

我想我会接受这个,尽管我可能不会使用它,因为它至少是我没有考虑过的技术。 – luksan

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) 
+0

呀,但是这需要这两种类型在源文件中是相邻的,这正是我试图避免的。尽管如此,仍然可能比需要将其直接放在类型上更好。 – luksan

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. 
+0

是的,那是我实际开始的,但我认为我认为这个消息是一个错误,而不是一个警告。尽管如此,你的方法可能是最直接的,即使它“被弃用”。我仍然好奇这里的其他人是否知道另一种简单但不被弃用的方式。 – luksan

+0

有趣的是,这只适用于我一次执行整个脚本(通过在VS中高亮显示并按ALT-ENTER)。如果我尝试单独执行类型定义和增强,我得到'错误FS0854:方法覆盖和接口实现在这里是不允许的,因为F#编译器不能覆盖已存在类型的方法。也许这种双重行为是他们不赞成这种行为的原因。 – luksan

+3

那么,编译时,类型*增加*会在同一个类中结束。而类型*扩展*最终作为扩展方法。然而,语法是相同的,所以我可以理解你必须同时执行它。 (不一定说,它非常直观) –