以下F#代码按预期工作,打印“匹配为'A':
let (|Char|_|) convf = function
| LazyList.Nil -> None
| LazyList.Cons (x, _) -> Some (convf x)
let test = function
| Char System.Char.ToUpper x -> printfn "Matched as %A" x
| _ -> printfn "Didn't match"
test (LazyList.of_list ['a'])
但是,如果我将Char
从部分活动模式更改为完整活动模式,如下所示:
let (|Char|NoChar|) convf = function
| LazyList.Nil -> NoChar
| LazyList.Cons (x, _) -> Char x
let test = function
| Char System.Char.ToUpper x -> printfn "Matched as %A" x
| NoChar System.Char.ToUpper -> printfn "Didn't match"
test (LazyList.of_list ['a'])
然后代码无法编译,给出以下错误消息:error FS0191: Only active patterns returning exactly one result may accept arguments.
这个例子可能看起来有点人为,但它是我试图在我业余时间工作的Prolog词法分析器中使用的活动模式的简化版本。我可以轻松地重写我的代码以避免这个问题,但我很好奇为什么不允许这种代码。
更新:较新版本的F#似乎已重命名此错误:
error FS0722: Only active patterns returning exactly one result may accept arguments
答案 0 :(得分:13)
NB。这正是Brian所说的,但希望以更清晰的方式说明。
我记得在这个问题和IIRC上记录了一个错误,这就是Don Syme就此事所说的话。
多案例活动模式是从某个输入值到多个输出值之一的转换函数。在您的示例中,任何字符都将转换为Char案例或NoChar案例。
这样做的好处是F#编译器调用一次多案例活动模式函数,然后通常可以确定下一个要评估的模式匹配规则。
但是,如果允许参数,则需要为每个模式匹配规则评估多案例活动模式。
想象一下
match input with
| Alpha "foo" -> ...
| Bravo "bar" -> ...
评估时(| Alpha | Bravo |)“foo”返回'Bravo',则第一条规则不匹配。 Likeways(| Alpha | Bravo |)“bar”返回'Alpha',则第二个规则也不匹配。所以你真的没有多案例活动模式。只是一个典型的,部分活跃的模式。 (因为对于某些输入,预期的模式案例不会被命中。)
因此,当面对一个没有多大意义的语言角落时,实际上可以通过部分参数化的活动模式使其更加清晰。该功能未添加到该语言中。
答案 1 :(得分:4)
我不能肯定地说(不知道实际的设计原理),但是试图对它进行逆向工程,你会期望这段代码做什么?
let (|Char|NoChar|) pred = function
| LazyList.Nil -> NoChar
| LazyList.Cons (x, _) -> if pred x then Char x else NoChar
let test = function
| Char System.Char.IsLetter x -> printfn "Matched as %A" x
| NoChar System.Char.IsDigit -> printfn "Didn't match"
test (LazyList.of_list ['a'])
test (LazyList.of_list ['1'])
鉴于非部分活动模式应该对整个空间进行分区,如果你在同一个匹配中为每个参数分配一个不同的参数,那将会很奇怪,因为它们可能“既失败”又“成功”。 (它也暗示了它们如何实现,例如在进行匹配之前捕获其参数的模式。捕获的参数在匹配的所有分支中都是不变的。)
它还表明你可以写例如。
let test convf l =
let (|Char|NoChar|) = function
| LazyList.Nil -> NoChar
| LazyList.Cons (x, _) -> Char(convf x)
match l with
| Char x -> printfn "Matched as %A" x
| NoChar -> printfn "Didn't match"
test System.Char.ToUpper (LazyList.of_list ['a'])
(虽然我不知道你的特定应用是否方便/逼真)。