Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
F# 不完全匹配和模式_F#_Pattern Matching_Active Pattern - Fatal编程技术网

F# 不完全匹配和模式

F# 不完全匹配和模式,f#,pattern-matching,active-pattern,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

我在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 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
我希望能够漂亮地打印表达式树,因此我执行了以下操作:

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
方法,因为它不是编译时安全的。因此,我将其改写为一个活动模式:

let (|Operator|_|) expr =
    match expr with
    | Add(_) -> Some "+"
    | Sub(_) | Neg(_) -> Some "-"
    | Mult(_) -> Some "*"
    | Div(_) -> Some "/"
    | Pow(_) -> Some "**"
    | _ -> None
现在我可以漂亮地重写我的
格式
函数,如下所示:

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 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#是魔法,这就行了。不幸的是,编译器随后警告我模式匹配不完整,因为它看不到匹配
一元(x)
的任何内容也将匹配
运算符(op)
,而匹配
二元(x,y)
的任何内容也将匹配
运算符(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

此外,依我看,如果你想成为类型安全的,你应该避免装箱。如果你真的想要两种情况,做两种模式。或者,像这里所做的那样,只需对稍后需要的类型进行投影。这样可以避免装箱,而是返回打印所需的内容。

我认为可以将
操作符设置为正常功能,而不是活动模式。因为运算符只是一个函数,它为
expr
提供一个运算符字符串,其中as
一元
二进制
终端
是表达式类型,因此对它们进行模式匹配是有意义的

let operator expr =
    match expr with
    | Add(_) -> "+"
    | Sub(_) | Neg(_) -> "-"
    | Mult(_) -> "*"
    | Div(_) -> "/"
    | Pow(_) -> "**"
    | Var(_) | Con(_) -> ""


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

我发现最好的解决方案是重新构造原始类型定义:

type UnOp = Neg
type BinOp = Add | Sub | Mul | Div | Pow
type Expr =
  | Int of int
  | UnOp of UnOp * Expr
  | BinOp of BinOp * Expr * Expr
然后可以在
UnOp
BinOp
类型上写入各种函数,包括选择运算符。您甚至可能希望将来将
BinOp
拆分为算术运算符和比较运算符


例如,我在2008年的(非免费)文章中使用了这种方法。

我认为这类问题不太可能得到解决。在一般情况下,需要解决停车问题。我认为最优雅的解决方案是添加一个额外的模式层,以便返回
一元(x,op)
。我实际上考虑过这样做,但我想让我的模式特定于一个用例(对表达式的算术进行分类并提取其参数)。在这种情况下,您确实放弃了一些编译时安全性,不过。只要您将
Expr
扩展到另一个case,您的
操作符
就会简单地返回(可能错误的)结果
,而不是给您一个编译器警告,您需要为新的case扩展函数。我想这可以通过不使用
case并指定每个case来解决(见我的最新答案)将它作为一个函数是我开始的方式。但这仍然不是编译时安全的,因为它只是返回一个空字符串,调用该函数没有任何意义。我几乎宁愿让它抛出一个异常,就像我最初所做的那样,所以至少我知道它被错误地调用了。但至少按照你的方式(明确列出每种类型),每当我更新我的联合时,我都会得到一个更新函数的警告。这个想法很有趣。我不知道我是否会使用它,因为我想保持我的联合简单,但如果我真的想要类型安全,这可能是最好的解决方案。