Кортежи и записи

Учимся группировать данные двумя способами: кортежами по позиции и записями по именам полей.

Кортеж объединяет фиксированное число значений разных типов по позиции; запись (record) — по именованным полям.

Любая программа работает со структурами: точка имеет координаты, пользователь — имя и возраст. OCaml предлагает два инструмента. Кортежи хороши для коротких безымянных группировок, записи — для долговечных доменных структур, где имена полей важны.

Кортежи

let point = (3, 4)              (* int * int *)
let person = ("Аня", 25, true) (* string * int * bool *)

Тип кортежа записывается через *: int * int. Для пар есть готовые fst и snd:

let x = fst point   (* 3 *)
let y = snd point   (* 4 *)

let dist (x1, y1) (x2, y2) =
  let dx = float_of_int (x2 - x1) in
  let dy = float_of_int (y2 - y1) in
  sqrt (dx *. dx +. dy *. dy)

Аргумент (x1, y1) прямо в заголовке функции разбирается на части — это сопоставление с образцом в действии.

Записи

type person = {
  name : string;
  age  : int;
  active : bool;
}

let anya = { name = "Аня"; age = 25; active = true }
let n = anya.name        (* доступ к полю через точку *)

По сравнению с кортежем запись самодокументируется — anya.age понятнее, чем snd anya, и порядок полей при создании не обязан совпадать с объявлением.

Функциональное обновление

Записи неизменяемы, но есть синтаксис «создать копию с изменённым полем» через with:

let older = { anya with age = 26 }
(* новая запись: name="Аня", age=26, active=true *)

Это не мутация: anya остаётся прежней, а older — новое значение. Вместо изменения мы порождаем обновлённую версию.

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

И кортеж, и запись представлены в памяти как блок последовательных полей — по сути одинаково. Разница чисто в синтаксисе и проверке типов: к полю записи компилятор обращается по известному смещению. Поэтому записи не медленнее кортежей. Функциональное обновление { r with f = v } аллоцирует новый блок и копирует поля, кроме изменённого. Имена полей принадлежат типу: если два типа имеют поле name, компилятор по умолчанию свяжет .name с последним объявленным типом, поэтому иногда нужна аннотация.

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

  • Менять поле записи на месте. По умолчанию поля неизменяемы; для мутации нужно mutable.
  • Путать = и : при создании записи. В типе поля описываются через :, при создании — через =.
  • Ждать, что with изменит исходную запись. Он создаёт новую копию.

Итоги

  • Кортеж группирует значения по позиции (тип через *); пары разбирают fst/snd.
  • Запись группирует по именованным полям; доступ через точку, самодокументируется.
  • { r with поле = значение } создаёт обновлённую копию, не меняя оригинал.
Проверьте себя
1. Чем запись (record) отличается от кортежа?
AЗапись быстрее
BЗапись группирует данные по именованным полям, кортеж — по позиции
CКортеж изменяемый, запись нет
DЗапись хранит только числа
2. Что делает выражение `{ anya with age = 26 }`?
AМеняет поле age в anya
BСоздаёт новую запись-копию с изменённым age
CУдаляет поле age
DВызывает ошибку
3. Как обратиться к полю name записи anya?
Aanya->name
Banya.name
Cname(anya)
Danya[name]