Можете ли вы вложить «двойные минусы» образец соответствия? - PullRequest
1 голос
/ 07 января 2009

Я хочу усилить шаблон, чтобы он соответствовал только числам, которые проходят дополнительную функцию проверки.

let (|IsValid|_|) n = ...

let (|Nil|One|Two|) (l : int list) =
    match l with 
    | a :: b :: t -> Two(a + b)
    | a :: t      -> One(a)
    | _           -> Nil

Дело «Один» легко:

    | IsValid(a) :: t -> One(a)

Случай «Два» не очевиден для меня. Необходимо проверить сумму чисел. Могу ли я сделать это без использования охраны?

...

Редактировать: я мог бы использовать when-guard (с функцией isValid, возвращающей bool) следующим образом:

    | a :: b :: t when isValid a + b -> Two(a + b)

Это менее элегантно, чем просто сопоставление с шаблоном; хуже, a + b применяется дважды.

Также обратите внимание, что это упрощенная версия моего фактического кода (например, я не пытаюсь просто сравнивать с различными длинами списка) - вопрос о вложенном сопоставлении по шаблону двойных конусов.

Ответы [ 2 ]

2 голосов
/ 09 января 2009

Мое решение: добавить «вспомогательный» распознаватель с возвращаемым значением, предназначенным для использования в родительском шаблоне:

let (|MatchTwo|_|) = function
    | a :: b :: t -> Some(a + b :: t)
    | _ -> None

Используйте это так:

let (|Nil|One|Two|) (l : int list) =
    match l with 
    | MatchTwo(IsValid(a) :: t) -> Two(a)
    |          IsValid(a) :: t  -> One(a)
    | _                         -> Nil
2 голосов
/ 07 января 2009

Когда вы делаете:

| a :: b :: t -> ... 

Вы не обязательно сопоставляете два элемента в списке. Лучше использовать [] вместо t для точного соответствия двух элементов - t может быть списком других элементов.

 | a :: b :: [] -> Two (a+b)

Это гарантирует, что вы сопоставляете два и только два элемента - проверка ошибок бесплатно! Я предлагаю сделать это, даже если вы ожидаете, что функция будет принимать только список из 0, 1 или 2 элементов. Таким образом,

EDIT:

let (|MatchTwo|_|) = function
    | a :: b :: t -> Some(a + b :: t)
    | _ -> None
let (|Nil|One|Two|) (l : int list) = match l with 
    | MatchTwo(IsValid(a) :: t) -> Two(a)
    | IsValid(a) :: t  -> One(a)
    | _ -> Nil

Да, используйте when. Это беспорядок. Сопоставление с образцом заключается в том, что применение функций в сопоставлении действительно не имеет смысла. Но примите во внимание то, что я упомянул ранее. На основании вашего примера:

match l with
| a :: b :: t when isValid (a+b) -> Two (a+b)
| a :: t when isValid (a) -> One a
| _ -> Nil

Второй шаблон будет соответствовать спискам длиной больше единицы, если isValid имеет значение false в первом шаблоне - будьте предупреждены. Будьте максимально конкретны в своих шаблонах, если вы хотите соответствовать одному элементу, сделайте это.

Если какая-либо операция, которую вы используете для объединения a и b (в данном случае +), является вычислительно дорогой, вам придется отбросить when и использовать оператор let перед проверкой isValid и возвращением типа варианта.

...