Активные паттерны

Последнее обновление: 02.01.2024

"Активные паттерны" или active patterns позволяют присвоить различным частям паттерна некоторые названия-идентификаторы. Через подобные идентификаторы затем можно ссылаться на соответствующие части паттерна при совпадении со сравниваемым выражением. Для их определения активных паттернов применяется следующий синтаксис:

(|идентификатор_1|идентификатор_2|идентификатор_N|)

В круглые скобки заключаются набор идентификаторов, каждый из которых помещается между вертикальными чертами и будет сопоставляться с определенным значением/набором значений.

Например, рассмотрим следующую программу:

let (|Odd|Even|) n = if n%2=0 then Even n else Odd n

for i in 1..5 do
  match i with
  | Odd _ -> printfn "odd"
  | Even _ -> printfn "even"

Здесь выражение (|Odd|Even|) представляет активный паттерн, который определяет два значения - Even и Odd. Каждое из этих значений хранит число - число n. С каким значением сопоставляется число n, определяется конструкцией

if n%2=0 then Even n else Odd n

То есть если число четно, то конструкция возвращает значение Even. Если число n нечетное - возвращается значение Odd.

Далее в конструкции match применяется этот паттерн. Для этого пробегаемся по последовательности от 1 до 5 и выбираем каждое ее число в переменную i. Далее пытаемся сопоставить переменную i со значениями Even и Odd.

Поскольку значения Even и Odd представляют число (число n), нам надо получить это число. Но в данном случае оно не важно, поэтому вместо него используется символ подстановки _.

Консольный вывод:

odd
even
odd
even
odd

Также мы можем явным образом получить число из обоих значений:

let (|Odd|Even|) n = if n%2=0 then Even n else Odd n

for i in 1..5 do
  match i with
  | Odd n1 -> printfn $"odd: {n1}"
  | Even n2 -> printfn $"even: {n2}"

В данном случае число из Odd получаем в переменную n1, а число из Even - в переменную n2. Консольный вывод программы:

odd: 1
even: 2
odd: 3
even: 4
odd: 5

Частичные активные паттерны

В примере выше был продемонстрирован "полный паттерн" - паттерн предполагает наличие двух значений, и для всех этих значений определяется шаблон сопоставления - четное число или нечетное. Но также могут быть "частичные паттерны". Они применяются, когда нужно сопоставить идентификатор только с частью значений. В этом случае также указывается набор идентифкаторов, каждый из которых соответствует некоторому значению входного выражения. А для остальных значений, которые не надо сопоставлять, применяется символ подчеркивания _ - он указывается в конце списка шаблонов. внутри зажимов-бананов. Следующий код иллюстрирует использование частично активного шаблона.

(|идентификатор_1|идентификатор_2|идентификатор_N|_|)

Например:

let (|Even|_|) n = if n%2=0 then Some n else None

for i in 1..5 do
  match i with
  | Even n2 -> printfn "%d" n2
  | _ -> () // ничего не делаем

Здесь задается только одно значение - Even ( допустим, второе для нас не играет никакой роли). В этом случае при сопоставлении с паттерном возвращается значение Some из встроенного объединения Option. Если же сопоставление прошло неудачно, возвращается значение None

Другой пример - определим паттерн, который соответствует числу, который равен своему квадратному корню в квадрате, например 4, 9, 16, 25 и т.д:

let (|Square|_|) n =
  let iroot = int(sqrt(float n))
  if iroot * iroot = n then Some (iroot, n) else None

for i in 1 .. 10 do 
  match i with 
  | Square (n, m) -> printfn "%d - %d" n m
  | _ -> ()

Здесь с помощью встроенной функции sqrt() получаем квадратный корень числа, которое должно представлять тип float (поэтому сначала идет преобразование входного числа в тип float). Затем результат преобразуем в тип int:

int(sqrt(float n))

То есть, к примеру, если передано число 9, то результатом будет число 3. Если передано число 10, то результатом также будет число 3 (квадратный корень 3.16227766017)

Затем полученный целочисленный квадратный корень умножаем на себя и проверяем полученное значение. Если оно равно параметру n, то возвращаем корень и значение n через Some.

В итоге мы получим следующий вывод:

1 - 1
2 - 4
3 - 9

Причем активные паттерны могут быть рекурсивными - то есть вызывать себя для сопоставления выражения. В этом случае паттерн как и функция предваряется ключевым словом rec. Например:

let (|Square|_|) n =
  let iroot = int(sqrt(float n))
  if iroot * iroot = n then Some iroot else None

let rec (|Squares|_|) l = 
  match l with 
  | [Square h] -> Some ([h])
  | (Square h)::(Squares tl) -> Some(h::tl)
  | _ -> None

let checkSquares nums = 
  match nums with 
  | Squares _ -> printfn "all squares!"
  | _ -> printfn "just numbers...."

checkSquares []   // just numbers....
checkSquares [2;4;9;16;25;36]   // just numbers....
checkSquares [4;9;16;25;36]   // all squares!

Вначале здесь тот же паттерн - Square, который проверяет квадратный корень числа, только для простоты через Some возвращаем сам корень.

Затем определен рекурсивный паттерн Squares, который принимает список и проверяет квадратный корень каждого элемента этого списка. Разберем этот паттерн. Вначале он сопоставляет список с одним элементом, который должен соответствовать паттерну Square:

match l with 
  | [Square h] -> Some ([h])

При успешном сопоставлении возвращаем список из этого элемента, обернутого в Some

Если в списке больше элементов, сопоставляем их с паттерном

(Square h)::(Squares tl) -> Some(h::tl)

Здесь первый элемент сопоставляется с паттерном Square, а оставшийся список рекурсивно сопоставляем с паттерном Squares. При успешном сопоставлении возвращается этот же список, обернутый в Some. Таким образом, в паттерне вызываем этот же паттерн.

Далее для проверки определяем функцию checkSquares, которая принимает список и сопоставляет с паттерном Squares.

let checkSquares nums = 
  match nums with 
  | Squares _ -> printfn "all squares!"
  | _ -> printfn "just numbers...."

Для теста проверяем три возможных варианта:

checkSquares []   // just numbers....
checkSquares [2;4;9;16;25;36]   // just numbers....
checkSquares [4;9;16;25;36]   // all squares!
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850