如果普通函数可以用作模式,就可以省去编写像
这样琐碎的活动模式。let (|NotEmpty|_|) s = Seq.tryPick Some s
,假设允许
let s = seq []
match s with
| Seq.tryPick Some -> ...
| _ -> //empty
这将使函数更具可重用性,消除了您想要使用匹配的"模式化"函数的需要:
let f x = if x then Some() else None
let (|F|_|) = f
我知道活动模式可以作为函数调用,所以前面的例子可以通过只定义模式来简化。但是放弃特殊的模式语法会简化这个过程。使用特殊语法的原因是什么?
编辑
在下面的示例中,活动模式将遮蔽文字。
[<Literal>]
let X = 1
let (|X|_|) x = if x = 0 then Some() else None
match 0 with //returns true
| X -> true
| _ -> false
为什么不能在模式内调用函数呢?
编辑2
我发现了一个模棱两可的场景
let zero n = if n = 0 then Some() else None
match 0 with
| zero -> //function call or capture?
在我看来,这澄清了为什么活动模式必须以大写字母开头——它使意图更清晰,并且使像我前面的例子那样的阴影更不可能发生。
回答这个问题的一种方法是说活动模式(作为一种特殊的语法类别)使得可以使用相同的名称来构造表达式中的值并在模式中解构它。
例如,假设我们使用类型Info
来表示一些信息:
type Info = I of string * int
可以编写两个函数来构造和析构这种类型的值:
val constructInfo : string * int -> Info
val destructInfo : Info -> string * int
在这种情况下,这两个函数的实现很简单,但有趣的是它们的类型签名是对偶的。构造函数接受值并创建(抽象)类型,析构函数接受类型并返回单个值。
使用活动模式,我们可以为这两个目的使用相同的名称Info
。这使得它与语言的其他部分保持一致——例如x::xs
和(a, b)
既是构造函数又是模式。f#库对f#引用做了类似的事情(即Lambda
是Expr
类型的模式和构造函数。
let Info (a, b) = I (a, b)
let (|Info|) (I (a, b)) = (a, b)
结果是相同的语法Info(a, b)
可以同时作为模式和表达式出现。
模式和let-bound变量具有不同的名称空间,考虑到阴影的发生频率和短标识符的使用频率,这在大多数情况下是有意义的。例如,您可能在程序的第一行定义x
,然后在200行之后定义match ... with | (x,y) -> x + y
,在这种情况下,您几乎肯定希望x
是一个新的标识符。
如果您想使用任意函数,只需使用参数化的活动模式:
let (|Id|_|) f x = f x
match seq [] with
| Id (Seq.tryPick Some) _ -> ...
编辑
关于名称解析的详细信息,请参阅规范中模式的名称解析。关键是有一个逻辑PatItems表,它不同于用于表达式中的名称的ExprItems表。在您在问题中添加到编辑的特定情况下,X
的最后一个定义获胜,因此在这种情况下它被视为活动模式(当X
出现在模式中时有效地遮蔽文字)。
除了名称冲突/阴影问题之外,我怀疑还有一些方法允许在模式中使用更广泛的表达式会导致歧义解析,尽管我不能立即想到任何方法。