Глава 11. Язык OCaml
9 Классы
- 9.1 Типы классов
- 9.2 Выражения классов
- 9.3 Определения классов
- 9.4 Спецификации классов
- 9.5 Определения типов классов
Классы определяются с помощью небольшого языка, похожего на язык модулей.
9.1 Типы классов
Типы классов являются эквивалентом выражений типов на уровне классов: они задают общую структуру и свойства типов классов.
|
См. также следующие расширения языка: атрибуты и узлы расширения.
Простые выражения классов
Выражение classtype-path эквивалентно типу класса, связанному с именем classtype-path. Аналогично, выражение [ typexpr1 , … typexprn ] classtype-path эквивалентно параметрическому типу класса, связанному с именем classtype-path, в котором параметры типов инстанцированы соответственно типами typexpr1, …typexprn.
Функциональный тип класса
Выражение типа класса typexpr -> class-type является типом функций классов (функций из значений в классы), принимающих в качестве аргумента значение типа typexpr и возвращающих в качестве результата класс типа class-type.
Тип тела класса
Выражение типа класса object [( typexpr )] { class-field-spec } end является типом тела класса. Оно задаёт его переменные экземпляра и методы. В этом типе typexpr сопоставляется с типом self, тем самым задавая имя для типа self.
Тело класса соответствует типу тела класса, если в нём определены все компоненты, указанные в типе тела класса, и эти определения удовлетворяют требованиям к типам, заданным в типе тела класса. Кроме того, все методы, как виртуальные, так и публичные, имеющиеся в теле класса, должны присутствовать и в типе тела класса (однако некоторые переменные экземпляра и конкретные приватные методы можно опустить). Виртуальный метод соответствует конкретному методу, что позволяет скрыть его реализацию. Неизменяемая переменная экземпляра соответствует изменяемой переменной экземпляра.
Локальные открытия
Локальные открытия поддерживаются в типах классов начиная с OCaml 4.06.
Наследование
Конструкция наследования inherit class-body-type позволяет включать методы и переменные экземпляра из других типов классов. Типы переменных экземпляра и методов из class-body-type добавляются в текущий тип класса.
Спецификация переменной экземпляра
Спецификация переменной экземпляра записывается как val [mutable] [virtual] inst-var-name : typexpr, где inst-var-name — имя переменной экземпляра, а typexpr — её ожидаемый тип. Флаг mutable указывает, можно ли физически изменять эту переменную экземпляра. Флаг virtual указывает, что эта переменная экземпляра не инициализирована. Её можно инициализировать позднее посредством наследования.
Спецификация переменной экземпляра скрывает все предыдущие спецификации переменных экземпляра с тем же именем.
Спецификация метода
Спецификация метода записывается как method [private] method-name : poly-typexpr, где method-name — имя метода, а poly-typexpr — его ожидаемый тип, возможно полиморфный. Флаг private указывает, что метод недоступен извне объекта.
Полиморфизм можно не указывать явно в спецификациях публичных методов: любая переменная типа, не связанная с параметром класса и не встречающаяся в других местах спецификации класса, считается универсальной и становится полиморфной в результирующем типе метода. Запись явного полиморфного типа отключает это поведение.
Если для одного и того же метода указано несколько спецификаций, их типы должны быть совместимы. Любая непубличная спецификация метода делает его публичным.
Спецификация виртуального метода
Спецификация виртуального метода записывается как method [private] virtual method-name : poly-typexpr, где method-name — имя метода, а poly-typexpr — его ожидаемый тип.
Ограничения параметров типов
Конструкция constraint typexpr1 = typexpr2 требует, чтобы два выражения типов были равны. Обычно это используется для задания параметров типов: таким образом их можно связать с конкретными выражениями типов.
9.2 Выражения классов
Выражения классов являются эквивалентом выражений значений на уровне классов: они вычисляются в классы, предоставляя тем самым реализации спецификаций, заданных в типах классов.
|
|
См. также следующие расширения языка: локально абстрактные типы, атрибуты и узлы расширения.
Простые выражения классов
Выражение class-path вычисляется в класс, связанный с именем class-path. Аналогично, выражение [ typexpr1 , … typexprn ] class-path вычисляется в параметрический класс, связанный с именем class-path, в котором параметры типов инстанцированы соответственно типами typexpr1, …typexprn.
Выражение ( class-expr ) вычисляется в тот же модуль, что и class-expr.
Выражение ( class-expr : class-type ) проверяет, соответствует ли class-type типу class-expr (то есть удовлетворяет ли реализация class-expr спецификации типа class-type). Всё выражение вычисляется в тот же класс, что и class-expr, за исключением того, что все компоненты, не указанные в class-type, скрываются и становятся недоступными.
Применение класса
Применение класса обозначается последовательной записью выражений (возможно, помеченных). Оно обозначает класс, конструктором которого является первое выражение, применённое к заданным аргументам. Аргументы вычисляются так же, как при применении выражения, но сам конструктор вычисляется только при создании объектов. В частности, побочные эффекты, вызванные применением конструктора, возникают только во время создания объекта.
Функция класса
Выражение fun [[?]label-name:]pattern -> class-expr вычисляется в функцию из значений в классы. При применении этой функции к значению v оно сопоставляется с образцом pattern, а результатом становится результат вычисления class-expr в расширенном окружении.
Преобразование функций со значениями по умолчанию в функции с образцами выполняется для функций классов так же, как и для обычных функций.
Выражение
является краткой формой записи для
Локальные определения
Конструкции let и let rec связывают имена значений локально, как и в выражениях основного языка.
Если локальное определение находится непосредственно в начале определения класса, оно вычисляется при создании класса (как если бы определение находилось вне класса). В противном случае оно вычисляется при вызове конструктора объекта.
Локальные открытия
Локальные открытия поддерживаются в выражениях классов начиная с OCaml 4.06.
Тело класса
|
Выражение object class-body end обозначает тело класса. Это прототип объекта: в нём перечислены переменные экземпляра и методы объекта данного класса.
Тело класса является значением класса: оно не вычисляется сразу. Вместо этого его компоненты вычисляются каждый раз при создании объекта.
В теле класса шаблон ( pattern [: typexpr] ) сопоставляется с self, тем самым создавая привязку для self и типа self. Self можно использовать только в методах и инициализаторах.
Тип self не может быть замкнутым типом объекта, чтобы класс оставался расширяемым.
Начиная с OCaml 4.01, повторное определение метода или переменной экземпляра с тем же именем в одном теле класса считается ошибкой.
Наследование
Конструкция наследования inherit class-expr позволяет повторно использовать методы и переменные экземпляра из других классов. Выражение класса class-expr должно вычисляться в тело класса. Переменные экземпляра, методы и инициализаторы из этого тела класса добавляются в текущий класс. Добавление метода переопределит любой ранее определённый метод с тем же именем.
Предка можно связать с именем, добавив as lowercase-ident к конструкции наследования. lowercase-ident не является настоящей переменной и может использоваться только для выбора метода, то есть в выражении lowercase-ident # method-name. Это позволяет получить доступ к методу method-name в том виде, в каком он был определён в родительском классе, даже если он переопределён в текущем классе. Область видимости этой привязки предка ограничена текущим классом. К методу предка можно обратиться из подкласса, но только косвенно.
Определение переменной экземпляра
Определение val [mutable] inst-var-name = expr добавляет переменную экземпляра inst-var-name, начальным значением которой является значение выражения expr. Флаг mutable позволяет методам физически изменять эту переменную.
Переменную экземпляра можно использовать только в методах и инициализаторах, следующих за её определением.
Начиная с версии 3.10, повторные определения видимой переменной экземпляра с тем же именем не создают новую переменную, а объединяются; для инициализации используется последнее значение. Они должны иметь одинаковые типы и атрибут изменяемости. Однако если переменная экземпляра скрыта, поскольку она не указана в интерфейсе, она останется отдельной от других переменных экземпляра с тем же именем.
Определение виртуальной переменной экземпляра
Спецификация переменной записывается как val [mutable] virtual inst-var-name : typexpr. Она указывает, может ли переменная изменяться, и задаёт её тип.
Виртуальные переменные экземпляра появились в версии 3.10.
Определение метода
Определение метода записывается как method method-name = expr. Определение метода переопределяет все его предыдущие определения. Метод будет открытым (то есть не приватным), если хотя бы в одном из определений он объявлен открытым.
Приватный метод, method private method-name = expr, можно вызывать только для self (из других методов того же объекта, определённых в этом классе или одном из его подклассов). Такой вызов выполняется с помощью выражения value-name # method-name, где value-name непосредственно связывается с self в начале определения класса. Приватные методы не входят в типы объектов. Метод может иметь как открытые, так и приватные определения, но как только появляется открытое определение, все последующие определения будут открытыми.
Методам можно явно задать полиморфный тип, что позволяет использовать их полиморфно в программах (даже для одного и того же объекта). Явное объявление можно выполнить одним из трёх способов: (1) указав явный полиморфный тип в определении метода сразу после имени метода, то есть method [private] method-name : { ' ident }+ . typexpr = expr; (2) предварительно объявив явный полиморфный тип в определении виртуального метода; (3) импортировав такое объявление с помощью наследования и/или ограничения типа self.
В телах методов доступны некоторые специальные выражения для работы с переменными экземпляра и создания копии self:
|
Выражение inst-var-name <- expr изменяет текущий объект на месте, заменяя значение, связанное с inst-var-name, значением expr. Разумеется, эта переменная экземпляра должна быть объявлена изменяемой.
Выражение {< inst-var-name1 = expr1 ; … ; inst-var-namen = exprn >} вычисляется в копию текущего объекта, в которой значения переменных экземпляра inst-var-name1, …, inst-var-namen заменены значениями соответствующих выражений expr1, …, exprn.
Определение виртуального метода
Спецификация метода записывается как method [private] virtual method-name : poly-typexpr. Она указывает, является ли метод открытым или приватным, и задаёт его тип. Если метод должен быть полиморфным, его тип необходимо явно объявить полиморфным.
Явное переопределение
Начиная с OCaml 3.12, ключевые слова inherit!, val! и method! имеют ту же семантику, что и inherit, val и method, но дополнительно требуют, чтобы вводимое ими определение было переопределением. В частности, method! требует, чтобы method-name уже был определён в этом классе, val! требует, чтобы inst-var-name уже было определено в этом классе, а inherit! требует, чтобы class-expr переопределяло какие-либо определения. Если переопределения не происходит, выдаётся ошибка.
Кроме того, эти три ключевых слова позволяют избежать предупреждений 7 (переопределение метода) и 13 (переопределение переменной экземпляра). Обратите внимание, что предупреждение 7 по умолчанию отключено.
Ограничения параметров типа
Конструкция constraint typexpr1 = typexpr2 требует, чтобы два выражения типа были равны. Обычно она используется для задания параметров типа: таким образом их можно связать с конкретными выражениями типа.
Инициализаторы
Инициализатор класса initializer expr задаёт выражение, которое вычисляется при каждом создании объекта из класса после инициализации всех его переменных экземпляра.
9.3 Определения классов
|
Определение класса class class-binding { and class-binding } является рекурсивным. Каждая привязка класса class-binding определяет имя класса class-name, которое можно использовать во всём выражении, кроме наследования. Его также можно использовать при наследовании, но только в определениях, следующих за его собственным.
Привязка класса связывает имя класса class-name со значением выражения class-expr. Она также связывает тип класса class-name с типом класса и определяет два сокращения типа: class-name и # class-name. Первое является типом объектов этого класса, а второе — более общим: оно унифицируется с типом любого объекта, принадлежащего подклассу (см. раздел 11.4).
Виртуальный класс
Класс необходимо пометить как виртуальный, если один из его методов является виртуальным (то есть указан в типе класса, но фактически не определён). Создавать объекты виртуального класса нельзя.
Параметры типа
Параметры типа класса соответствуют параметрам типа самого класса и двух сокращений типа, определяемых привязкой класса. В определении класса их необходимо связать с конкретными типами с помощью ограничений типов. Чтобы сокращения были корректно сформированы, переменные типа в выведенном типе класса должны быть либо параметрами типа, либо связанными в предложении ограничений.
9.4 Спецификации классов
|
Это соответствие определений классов в сигнатурах. Спецификация класса соответствует определению класса, если у них одинаковые параметры типа и их типы соответствуют друг другу.
9.5 Определения типов классов
|
Определение типа класса class class-name = class-body-type задаёт сокращение class-name для типа тела класса class-body-type. Как и для определений классов, также определяются два сокращения типа: class-name и # class-name. Определение можно параметризовать несколькими параметрами типа. Если какой-либо метод в теле типа класса является виртуальным, определение необходимо пометить как virtual.
Два определения типа класса соответствуют друг другу, если у них одинаковые параметры типа и они разворачиваются в соответствующие типы.
© 1995-2026 INRIA.
https://ocaml.org/manual/5.5/classes.html