option вместо null

Решаем «проблему на миллиард долларов» — отсутствие значения — типобезопасно, с помощью типа option.

option — стандартный вариантный тип None | Some of 'a, который явно выражает «значение может отсутствовать».

Тони Хоар назвал изобретение null своей «ошибкой на миллиард долларов»: ссылка, которая может оказаться пустой, но об этом не сказано в типе, порождает бесконечные падения. В OCaml нет null вообще. Отсутствие значения выражается типом option — и компилятор заставляет обработать пустой случай.

Определение и использование

type 'a option = None | Some of 'a

Функция, которая может не вернуть результат, возвращает 'a option:

let safe_div a b =
  if b = 0 then None
  else Some (a / b)

let show = function
  | None -> "деление на ноль"
  | Some q -> "результат: " ^ string_of_int q

Тип safe_divint -> int -> int option. Вызывающий код обязан разобрать оба случая через match.

Где option встречается

Многие стандартные функции возвращают option вместо исключения. List.find_opt ищет элемент и возвращает None, если не нашёл; List.assoc_opt ищет по ключу. Суффикс _opt — соглашение «вернёт option вместо исключения».

let users = [(1, "Аня"); (2, "Иван")]
let name = List.assoc_opt 2 users    (* Some "Иван" *)
let miss = List.assoc_opt 9 users    (* None *)

Удобная работа: модуль Option

Option.value (Some 5) ~default:0   (* 5 *)
Option.value None ~default:0       (* 0 *)
Option.map (fun x -> x * 2) (Some 3)  (* Some 6 *)

Option.map применяет функцию «внутри» Some, оставляя None как есть. Option.bind сцепляет операции, каждая из которых сама может вернуть None.

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

None компилятор представляет как целое-константу, а Some x — как блок с одним полем. Накладные расходы минимальны. Главная ценность в системе типов: тип int option и int — разные, и компилятор не позволит использовать «возможно отсутствующее» число там, где нужно гарантированное. В отличие от null, который имеет тип любого объекта, option делает возможное отсутствие частью контракта. Это та же идея, что Option/Maybe в Haskell, F# и Scala — все они пришли из ML.

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

  • Распаковывать option «насильно». Не игнорируйте None, обрабатывайте его.
  • Возвращать «магические» значения вроде -1 вместо None. Это возвращает все проблемы null.
  • Путать Some x и x. Это значения разных типов; чтобы добраться до x, нужно разобрать Some.

Итоги

  • option (None | Some of 'a) делает отсутствие значения явным в типе — null в OCaml не существует.
  • Компилятор требует обработать случай None, устраняя целый класс ошибок.
  • Модуль Option (value, map, bind) упрощает работу без громоздких match.
Проверьте себя
1. Как устроен тип option в OCaml?
AЭто псевдоним для null
BВариантный тип None | Some of 'a
CИзменяемая ячейка
DУказатель, который может быть пустым
2. Почему option безопаснее null?
AОн быстрее
BТип 'a option отличается от 'a, и компилятор требует обработать None
CОн занимает меньше памяти
DОн автоматически подставляет 0
3. Что означает суффикс `_opt` в именах вроде List.find_opt?
AФункция необязательна
BФункция возвращает option вместо выбрасывания исключения
CФункция оптимизирована
DАргумент опционален