luk*_*san 5 f# pattern-matching active-pattern
我在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
Run Code Online (Sandbox Code Playgroud)
我希望能够漂亮打印表达式树,所以我做了以下事情:
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
Run Code Online (Sandbox Code Playgroud)
但是,我并不喜欢该函数的failwith方法,operator因为它不是编译时安全的.所以我把它重写为一个活跃的模式:
let (|Operator|_|) expr =
match expr with
| Add(_) -> Some "+"
| Sub(_) | Neg(_) -> Some "-"
| Mult(_) -> Some "*"
| Div(_) -> Some "/"
| Pow(_) -> Some "**"
| _ -> None
Run Code Online (Sandbox Code Playgroud)
现在我可以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
Run Code Online (Sandbox Code Playgroud)
我假设,因为F#是神奇的,这才有效.不幸的是,编译器然后警告我不完整的模式匹配,因为它不能看到Unary(x)匹配的Operator(op)任何东西也匹配,匹配的任何东西Binary(x, y)也匹配Operator(op).我认为像这样的警告和编译器错误一样糟糕.
所以我的问题是:是否有一个特定的原因,为什么这不起作用(就像我在某处留下了一些神奇的注释或者是否有一些我没有看到的东西)?有没有一个简单的解决方法,我可以用来获得我想要的安全类型?这种类型的编译时检查是否存在固有问题,或者F#可能会在未来的某个版本中添加?
如果将基本术语和复杂术语之间的目的地编码到类型系统中,则可以避免运行时检查并使它们成为完整的模式匹配。
type Num = int
type Name = string
type GroundTerm =
| Con of Num
| Var of Name
type ComplexTerm =
| Add of Term * Term
| Sub of Term * Term
| Mult of Term * Term
| Div of Term * Term
| Pow of Term * Term
| Neg of Term
and Term =
| GroundTerm of GroundTerm
| ComplexTerm of ComplexTerm
let (|Operator|) ct =
match ct with
| Add(_) -> "+"
| Sub(_) | Neg(_) -> "-"
| Mult(_) -> "*"
| Div(_) -> "/"
| Pow(_) -> "**"
let (|Unary|Binary|) ct =
match ct 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)
let (|Terminal|) gt =
match gt with
| Con x -> Terminal(string x)
| Var x -> Terminal(string x)
let rec format expr =
match expr with
| ComplexTerm ct ->
match ct 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)
| GroundTerm gt ->
match gt with
| Terminal(x) -> x
Run Code Online (Sandbox Code Playgroud)
另外,在我看来,如果你想类型安全,你应该避免拳击。如果您确实想要这两种情况,请制作两个图案。或者,正如此处所做的那样,只需对稍后需要的类型进行投影即可。这样您就可以避免装箱,而是返回打印所需的内容。