Подтвердить что ты не робот

Почему в F # нельзя параметризовать не частичные активные шаблоны?

Следующий код 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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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