我已经定义了以下歧视联盟:
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
虽然现在 sprintf
和 ToString
都输出相同的字符串,但如果我想要它,则无法获得 Add (Con 2,Con 3)
输出而不是 (2 + 3)
.
那么有什么其他方法可以做我想做的事情吗?
附:我还注意到,如果我将 StructuredFormatDisplay
属性放在扩充而不是原始类型上,它对我来说似乎没有问题 . 似乎F#编译器应该将属性添加到类型定义中,或者禁用类型augmentations上的属性 .
3 回答
实际上,
stringify
必须随数据类型一起增长,否则最终会出现不完整的模式匹配 . 对数据类型进行任何必要的修改都需要修改stringify
. 作为个人意见,我会考虑将两者放在同一个地方,除非项目非常复杂 .但是,由于您希望清楚DU类型,请考虑将数据类型包装到单个案例DU中:
来自this answer的引文:
您是否考虑过在扩充中定义
ToString
?然而,它确实具有丑陋的副作用
一个甚至不需要类型扩展的解决方案怎么样?
相反,定义一个带有字符串化的静态成员的类型(我们需要虚拟类型,因为
type a ... and b
需要b
是一个类型