"Активные паттерны" или 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!