ООП в OCaml: классы и объекты
Знакомимся с третьей парадигмой OCaml — объектно-ориентированной, и понимаем, почему буква «O» в названии используется реже всего.
В OCaml есть полноценная объектная система с классами, наследованием и структурной типизацией, но на практике её применяют редко, предпочитая модули и алгебраические типы.
Название OCaml расшифровывается как «Objective Caml» — объектная система была добавлена в 1990-х. Любопытно, что именно эта возможность используется реже остальных: большинство задач, для которых в Java берут классы, в OCaml элегантнее решаются модулями, функторами и вариантными типами.
Классы и объекты
class counter init = object
val mutable count = init
method get = count
method incr = count <- count + 1
end
let c = new counter 0
let () = c#incr; c#incr
let v = c#get (* 2 *)
val mutable count — изменяемое поле объекта, method — методы. Объект создаётся через new, вызов метода — через # (а не точку, чтобы не путать с полями записей и модулями).
Наследование
class resettable_counter init = object
inherit counter init as super
method reset = ignore super; count <- 0
end
inherit подключает родительский класс, as super даёт к нему доступ. Поддерживаются виртуальные методы и множественное наследование.
Структурная типизация — главная особенность
Самое необычное — структурная типизация объектов. Тип объекта определяется не классом, а набором методов. Функция, которой нужен объект с методом get : int, примет любой такой объект:
let describe obj = Printf.sprintf "значение: %d" obj#get
(* тип: < get : int; .. > -> string *)
Запись < get : int; .. > означает «любой объект с методом get : int и, возможно, другими». Это «утиная типизация», но проверяемая статически.
Почему ООП используется редко
Инкапсуляцию даёт модульная система с абстрактными типами. Полиморфизм — параметрические типы и функторы. Моделирование вариантов состояния — алгебраические типы с проверкой исчерпывающности, что объектам недоступно. Объекты выигрывают лишь в узких случаях: открытая расширяемость с поздним связыванием или структурная типизация. Поэтому в типичном OCaml-коде объектов почти нет.
Как работает под капотом
Вызов метода obj#m — это поиск метода в таблице методов объекта (с кешированием), то есть позднее связывание. Структурная типизация реализована через вывод типов: компилятор собирает требования к методам из тела функции и формирует «открытый» тип объекта с ... Это сложнее для тайпчекера, чем номинальные классы Java, и одна из причин громоздких сообщений об ошибках — ещё довод предпочесть модули.
Частые ошибки
- Вызывать метод через точку. Методы вызываются через
#:obj#get. - Тянуться к классам по привычке из Java. Сначала проверьте, не решается ли задача модулем или вариантным типом.
- Ждать номинальную типизацию. Объекты типизируются структурно.
Итоги
- OCaml имеет полную объектную систему: классы,
new, методы через#, наследованиеinherit. - Объекты типизируются структурно — по набору методов, а не по имени класса.
- На практике ООП применяют редко: модули, функторы и алгебраические типы обычно идиоматичнее.