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

bca*_*cat 13 .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'])
Run Code Online (Sandbox Code Playgroud)

但是,如果我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'])
Run Code Online (Sandbox Code Playgroud)

然后代码无法编译,给出以下错误消息: 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

Chr*_*ith 13

NB.这正是Brian所说的,但希望以更清晰的方式说明.

我记得正是在这个问题上记录了一个错误,而IIRC这就是Don Syme对此事所说的话.

多案例活动模式是从某个输入值到几个输出值之一的转换函数.在您的示例中,任何字符都将转换为Char案例或NoChar案例.

这样做的好处是F#编译器调用一次多案例活动模式函数,然后通常可以确定下一个要评估的模式匹配规则.

但是,如果允许参数,则需要为每个模式匹配规则评估多案例活动模式.

想象一下以下内容

match input with
| Alpha "foo" -> ...
| Bravo "bar" -> ...
Run Code Online (Sandbox Code Playgroud)

当评估(| Alpha | Bravo |)"foo"返回'Bravo'时,第一个规则将不匹配.Likeways(| Alpha | Bravo |)"bar"返回'Alpha',则第二个规则也不匹配.所以你真的没有多案例活动模式.只是一个典型的,部分活跃的模式.(因为对于某些输入,预期的模式案例不会被命中.)

因此,当遇到语言的一个角落并没有多大意义时,实际上可以通过部分参数化的活动模式使其更加清晰.该功能未添加到该语言中.


Bri*_*ian 5

我不能肯定(不知道实际的设计原理),但是尝试对其进行逆向工程,您希望这段代码做什么?

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'])
Run Code Online (Sandbox Code Playgroud)

鉴于非部分活动模式应该划分整个空间,如果你在同一个匹配中给每个不同的参数会很奇怪,因为那样它们可能“都失败”或“都成功”。(这也暗示了它们如何实现,例如,就像在进行匹配之前捕获其参数的模式一样。捕获的参数在匹配的所有分支中都是不变的。)

它还建议您可以编写例如

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'])
Run Code Online (Sandbox Code Playgroud)

(虽然我不知道这对于您的特定应用程序是否方便/现实)。