Статьи

Активная модель

На прошлой неделе кто-то указал мне на активные шаблоны в F #. И это было довольно интересное открытие.

Активные шаблоны используются в F # для разделения данных. Эти разделы затем могут быть использованы с сопоставлением с образцом. Веб-страница Microsoft сравнивает их с дискриминационными союзами.

Как их можно использовать? Ну, вы можете посмотреть по ссылке выше, или просто перейти по этому посту (или, по сути, сделать оба).

Ниже приведена начальная версия тривиальной функции, использующей сопоставление с образцом.

1
2
3
4
5
6
let thisNumberTrait(number) =
    match number with
        | x when x = 0 -> "Is Zero!!"
        | x when x > 0 -> "Is Positive!!"
        | x when x < 0 -> "Is Negative!!"
        | _ -> "I shouldn't be here"

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

01
02
03
04
05
06
07
08
09
10
let isZero(number) = number = 0
let isPositive(number) = number > 0
let isNegative(number) = number < 0
 
let thisNumberTrait(number) =
    match number with
        | x when isZero(x) -> "Is Zero!!"
        | x when isPositive(x) -> "Is Positive!!"
        | x when isNegative(x) -> "Is Negative!!"
        | _ -> "I shouldn't be here"

Но что, если бы мы могли полностью удалить пункты охраны? Вот где в игру вступают активные паттерны.

01
02
03
04
05
06
07
08
09
10
let (|Zero|_|) (number) = if number = 0 then Some(number) else None
let (|Positive|_|) (number) = if number > 0 then Some(number) else None
let (|Negative|_|) (number) = if number < 0 then Some(number) else None
 
let thisNumberTrait(number) =
    match number with
        | Zero(x) -> "Is Zero!!"
        | Positive(x) -> "Is Positive!!"
        | Negative(x) -> "Is Negative!!"
        | _ -> "I shouldn't be here"

Как видите, в коде есть несколько отличий. Если мы сконцентрируемся на логических функциях, то увидим, что теперь имя функции было изменено для конструкции (| | |) , и теперь она возвращает тип Option. Затем сопоставление с образцом заменило код x when ... на новый Pattern Zero(x) . Мы немного усложняем функции, упрощаем сопоставление с образцом.

Если мы посмотрим, какой ответ мы даем, мы поймем, что мы вообще не используем число, поэтому на самом деле мы можем изменить строку

1
| Zero(x) -> "Is Zero!!"

за

1
| Zero(_) -> "Is Zero!!"

x — это не данные, которые мы передаем в Pattern, это данные, которые возвращаются! Мы передаем данные косвенность. Но подождите, если я не буду использовать возвращаемые данные, нужно ли вообще их возвращать? И ответ нет. Так что теперь мы заменим

1
let (|Zero|_|) (number) = if number = 0 then Some(number) else None

с участием

1
let (|Zero|_|) (number) = if number = 0 then Some(Zero) else None

Мы возвращаем имя шаблона, а не данные.

Что теперь означает, что в матче мы можем изменить

1
| Zero(_) -> "Is Zero!!"

в

1
| Zero -> "Is Zero!!"

До сих пор мы возвращали те же данные, которые мы передали, и ничего не возвращали. Но что, если мы хотим вернуть данные другого типа? Это возможно: посмотрите ниже, что (|Positive|_|) теперь возвращает строку.

01
02
03
04
05
06
07
08
09
10
let (|Zero|_|) (number) = if number = 0 then Some(Zero) else None
let (|Positive|_|) (number) = if number > 0 then Some("Positive") else None
let (|Negative|_|) (number) = if number < 0 then Some(number) else None
 
let thisNumberTrait(number) =
    match number with
        | Zero -> "Is Zero!!"
        | Positive(x) -> sprintf "Is %s!!" x
        | Negative(_) -> "Is Negative!!"
        | _ -> "I shouldn't be here"

До сих пор я использовал частичные паттерны. То есть данные не могут быть определены как раздел. Вот почему есть подчеркивание ( (|Zero|_|) ) и почему мы возвращаем Option (Some | None). Но у нас может быть полный активный шаблон, где данные должны находиться внутри одного из разделов. Изменения легко, как показано ниже)

1
let (|Zero|_|) (number) = if number = 0 then Some(Zero) else None

в

1
let (|Zero|NonZero|) (number) = if number = 0 then Zero else NonZero

Шаблоны можно комбинировать, используя & для комбинации и, и | для или комбинации.

Так что вместо

1
| Positive(x) -> sprintf "Is %s!!" x

мы могли бы написать

1
| NonZero & Positive(x) -> sprintg "Is %s!!" x

Не то, чтобы в этом случае была какая-то разница, но это возможно.

Наконец, мы могли бы сделать один большой шаблон, заменив

1
2
3
let (|Zero|_|) (number) = if number = 0 then Some(Zero) else None
let (|Positive|_|) (number) = if number > 0 then Some(Positive) else None
let (|Negative|_|) (number) = if number < 0 then Some(Negative) else None

с участием

1
let (|Zero|Positive|Negative|) (number) = if number = 0 then Zero elif number > 0 then Positive else Negative

Что в этом случае выглядит для меня немного бессмысленно, так как я почти вернулся к исходному коду. Это будет его использовать, хотя.

Активные шаблоны — это интересная конструкция, особенно при многократном использовании активного шаблона.

Опубликовано на Java Code Geeks с разрешения Сандро Манкузо, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Active Pattern

Мнения, высказанные участниками Java Code Geeks, являются их собственными.