Разбор структур: списки, кортежи, вложенность

Сила сопоставления — в разборе структуры данных: достаём части значения прямо в образце.

Деструктуризация — разбор составного значения на части прямо в образце, с одновременной привязкой имён к этим частям.

Разбор кортежей

Образец может повторять форму кортежа и связывать имена с компонентами.

let classify point =
    match point with
    | (0, 0) -> "начало координат"
    | (x, 0) -> sprintf "на оси X в %d" x
    | (0, y) -> sprintf "на оси Y в %d" y
    | (x, y) -> sprintf "точка (%d, %d)" x y
printfn "%s" (classify (5, 0))
printfn "%s" (classify (3, 4))

Вывод:

на оси X в 5
точка (3, 4)

Имена в образце (x, y) привязываются к значениям и доступны в правой части ветки.

Разбор списков: head :: tail

Список можно разобрать на «голову» (первый элемент) и «хвост» (остальное) оператором ::. Это основа рекурсивной обработки списков.

let rec describe lst =
    match lst with
    | [] -> "пустой"
    | [x] -> sprintf "один элемент: %d" x
    | head :: tail -> sprintf "первый %d, ещё %d элементов" head (List.length tail)
printfn "%s" (describe [])
printfn "%s" (describe [42])
printfn "%s" (describe [1; 2; 3])

Вывод:

пустой
один элемент: 42
первый 1, ещё 2 элементов

Образцы [] (пустой), [x] (ровно один) и head :: tail (хотя бы один) покрывают все варианты списка.

Вложенные образцы

Образцы вкладываются друг в друга, разбирая структуру на любую глубину.

let firstTwo lst =
    match lst with
    | a :: b :: _ -> sprintf "%d и %d" a b
    | _ -> "меньше двух элементов"
printfn "%s" (firstTwo [10; 20; 30])

Вывод:

10 и 20

a :: b :: _ читается как «элемент a, затем b, затем любой хвост».

Образец as

Иногда нужно и разобрать значение, и сохранить его целиком. Ключевое слово as привязывает имя ко всему совпавшему фрагменту.

let inspect lst =
    match lst with
    | (x :: _) as whole -> sprintf "начинается с %d, длина %d" x (List.length whole)
    | [] -> "пусто"
printfn "%s" (inspect [7; 8; 9])

Вывод:

начинается с 7, длина 3

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

Список в F# — это связный список из ячеек, где каждая ячейка хранит голову и ссылку на хвост (или пустой список в конце). Образец head :: tail — это проверка «ячейка непуста» с извлечением её полей, а [] — проверка на пустой конец. Поэтому head :: tail работает за константное время — это просто чтение полей ячейки, без копирования.

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

  • Путать [x] (список из одного элемента) и x :: _ (минимум один элемент).
  • Забывать ветку [] при рекурсивном разборе — иначе неполнота.
  • Думать, что head :: tail копирует хвост — он лишь ссылается на него.

Итоги

  • Образцы повторяют форму данных и привязывают имена к частям.
  • Списки разбирают через [], [x], head :: tail.
  • Образцы вкладываются (a :: b :: _) для разбора на глубину.
  • as сохраняет совпавший фрагмент целиком под именем.
Проверьте себя
1. Что делает образец head :: tail для списка?
AСкладывает элементы
BРазбирает список на первый элемент и остаток
CСортирует список
DУдаляет первый элемент навсегда
2. Чем отличается образец [x] от x :: _?
AНичем
B[x] — ровно один элемент, x :: _ — минимум один элемент
C[x] — пустой список
Dx :: _ работает только для кортежей
3. Что делает ключевое слово as в образце?
AПреобразует тип
BПривязывает имя ко всему совпавшему фрагменту
CСоздаёт копию
DЗавершает match