为什么不能在F#中参数化非部分活动模式?

时间:2009-08-28 23:06:24

标签: .net syntax f# design-patterns

以下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

2 个答案:

答案 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'])

(虽然我不知道你的特定应用是否方便/逼真)。