Почему не частичные активные шаблоны могут быть параметризованы в F #? - PullRequest
13 голосов
/ 29 августа 2009

Следующий код F # работает как я ожидал, печатая `Matched as '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 ]

13 голосов
/ 29 августа 2009

NB. Это именно то, что сказал Брайан, но, как мы надеемся, изложено более четко.

Я помню, как регистрировал ошибку именно по этой проблеме, и IIRC это то, что Дон Сайм сказал по этому вопросу.

Активный шаблон с несколькими случаями - это функция преобразования некоторого входного значения в одно из нескольких выходных значений. В вашем примере любой символ преобразуется в регистр Char или NoChar.

Преимущество этого заключается в том, что компилятор F # вызывает функцию с несколькими падежами активных шаблонов один раз и затем может в целом определить, какое правило сопоставления шаблонов следует оценить следующим.

Однако, если вы разрешите параметр, вам нужно будет оценить активный случай с несколькими падежами для каждого правила сопоставления с образцом.

Итак, представьте следующее

match input with
| Alpha "foo" -> ...
| Bravo "bar" -> ...

При оценке (| Alpha | Bravo |) «foo» возвращает «Bravo», тогда первое правило не будет совпадать. Likeways (| Alpha | Bravo |) «bar» возвращает «Alpha», тогда второе правило также не будет соответствовать. Таким образом, у вас нет активного шаблона с несколькими случаями. Просто парамтеризованный, частичный активный паттерн. (Потому что для некоторых входных данных ожидаемый шаблонный случай не будет достигнут.)

Так что, когда сталкиваешься с углом языка, который не имеет большого смысла, и на самом деле может быть намного понятнее с помощью частичного параметризованного активного шаблона. Функция не была добавлена ​​к языку.

4 голосов
/ 29 августа 2009

Я не могу сказать наверняка (не знаю фактического обоснования дизайна), но, пытаясь его перепроектировать, что вы ожидаете от этого кода?

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

(хотя я не знаю, удобно ли это / реалистично для вашего конкретного приложения).

...