Активные шаблоны (active patterns)

Уникальная фишка F#: создаём собственные образцы для match — с произвольной логикой разбора.

Активный шаблон (active pattern) — пользовательская функция-разбиратель, которую можно использовать как образец в match, придавая ему семантику предметной области.

Зачем активные шаблоны

Обычные образцы разбирают структуру данных «как есть». Но иногда хочется сопоставлять по смыслу: «чётное/нечётное», «валидный email», «попадает в диапазон». Активные шаблоны позволяют завернуть произвольную логику в имя-образец и использовать его как встроенный.

Полный активный шаблон

Объявляется в «банановых скобках» (| ... |). Он делит вход на несколько именованных случаев.

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

let classify n =
    match n with
    | Even -> sprintf "%d чётное" n
    | Odd  -> sprintf "%d нечётное" n

printfn "%s" (classify 4)
printfn "%s" (classify 7)

Вывод:

4 чётное
7 нечётное

Теперь Even и Odd читаются в match как полноценные образцы, хотя за ними стоит наша логика.

Частичный активный шаблон

Когда вход подходит не всегда, используют частичный шаблон: он возвращает option (Some — совпало, None — нет). Его имя оформляют как (|Имя|_|).

let (|Positive|_|) n =
    if n > 0 then Some n else None

let sign n =
    match n with
    | Positive x -> sprintf "положительное %d" x
    | _ -> "не положительное"

printfn "%s" (sign 5)
printfn "%s" (sign -2)

Вывод:

положительное 5
не положительное

Параметризованный шаблон

Активный шаблон может принимать аргументы — настраиваемый разбор.

let (|DivisibleBy|_|) divisor n =
    if n % divisor = 0 then Some () else None

let fizz n =
    match n with
    | DivisibleBy 15 -> "FizzBuzz"
    | DivisibleBy 3 -> "Fizz"
    | DivisibleBy 5 -> "Buzz"
    | _ -> string n

printfn "%s" (fizz 15)
printfn "%s" (fizz 9)

Вывод:

FizzBuzz
Fizz

Как работает под капотом

Активный шаблон — это обычная функция, которую компилятор вызывает при сопоставлении. Полный шаблон возвращает значение специального choice-типа (какой случай выбран), частичный — option. Компилятор подставляет вызов этой функции в дерево решений match. Поэтому активные шаблоны типобезопасны и могут участвовать в любых образцах наравне со встроенными.

Частые ошибки

  • Ожидать проверку исчерпывающности для частичных шаблонов — её нет, нужна ветка _.
  • Делать активные шаблоны с тяжёлыми побочными эффектами — они вызываются при сопоставлении.
  • Путать полный ((|A|B|)) и частичный ((|A|_|)) синтаксис.

Итоги

  • Активные шаблоны добавляют в match собственные образцы с произвольной логикой.
  • Полный шаблон (|A|B|) делит вход на несколько случаев.
  • Частичный (|A|_|) возвращает option и требует ветку _.
  • Параметризованные шаблоны принимают аргументы для настраиваемого разбора.
Проверьте себя
1. Что такое активный шаблон?
AЦикл по образцам
BПользовательский образец с собственной логикой разбора для match
CСпособ кэширования
DТип данных для дат
2. Что возвращает частичный активный шаблон (|Positive|_|)?
Abool
Boption (Some — совпало, None — нет)
Cстроку
Dничего
3. Что обязательно нужно при использовании частичного шаблона в match?
AНичего особенного
BВетка _ для несовпавших случаев
CЦикл
DАннотация типа