Разбор структур: списки, кортежи, вложенность
Сила сопоставления — в разборе структуры данных: достаём части значения прямо в образце.
Деструктуризация — разбор составного значения на части прямо в образце, с одновременной привязкой имён к этим частям.
Разбор кортежей
Образец может повторять форму кортежа и связывать имена с компонентами.
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сохраняет совпавший фрагмент целиком под именем.