【发布时间】:2013-08-04 21:16:00
【问题描述】:
我在 F# 中定义了一个表达式树结构如下:
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
| Neg of Expr
我希望能够漂亮地打印表达式树,所以我做了以下操作:
let (|Unary|Binary|Terminal|) expr =
match expr with
| Add(x, y) -> Binary(x, y)
| Sub(x, y) -> Binary(x, y)
| Mult(x, y) -> Binary(x, y)
| Div(x, y) -> Binary(x, y)
| Pow(x, y) -> Binary(x, y)
| Neg(x) -> Unary(x)
| Con(x) -> Terminal(box x)
| Var(x) -> Terminal(box x)
let operator expr =
match expr with
| Add(_) -> "+"
| Sub(_) | Neg(_) -> "-"
| Mult(_) -> "*"
| Div(_) -> "/"
| Pow(_) -> "**"
| _ -> failwith "There is no operator for the given expression."
let rec format expr =
match expr with
| Unary(x) -> sprintf "%s(%s)" (operator expr) (format x)
| Binary(x, y) -> sprintf "(%s %s %s)" (format x) (operator expr) (format y)
| Terminal(x) -> string x
但是,我不太喜欢 failwith 函数的 operator 方法,因为它不是编译时安全的。所以我把它改写成一个主动模式:
let (|Operator|_|) expr =
match expr with
| Add(_) -> Some "+"
| Sub(_) | Neg(_) -> Some "-"
| Mult(_) -> Some "*"
| Div(_) -> Some "/"
| Pow(_) -> Some "**"
| _ -> None
现在我可以漂亮地重写我的format 函数如下:
let rec format expr =
match expr with
| Unary(x) & Operator(op) -> sprintf "%s(%s)" op (format x)
| Binary(x, y) & Operator(op) -> sprintf "(%s %s %s)" (format x) op (format y)
| Terminal(x) -> string x
我认为,因为 F# 很神奇,所以它会起作用。不幸的是,编译器会警告我不完整的模式匹配,因为它看不到匹配Unary(x) 的任何内容也将匹配Operator(op) 并且匹配Binary(x, y) 的任何内容也将匹配Operator(op)。而且我认为这样的警告与编译器错误一样糟糕。
所以我的问题是:这不起作用是否有特定原因(例如我是否在某处留下了一些神奇的注释,或者有什么我只是没有看到)?有没有一种简单的解决方法可以用来获得我想要的安全类型?这种类型的编译时检查是否存在固有问题,还是 F# 可能会在未来的某个版本中添加?
【问题讨论】:
-
我认为这种问题不太可能解决。在一般情况下,它需要解决停机问题。我认为最优雅的解决方案是添加额外的模式层,以便您返回
Unary(x,op)。 -
我实际上考虑过这样做,但我想让我的模式特定于一个用例(对表达式的数量进行分类并提取其参数)。
标签: f# pattern-matching active-pattern