Глава 11 Язык программирования OCaml
9 Классы
- 9.1 Типы классов
- 9.2 Выражения классов
- 9.3 Определения классов
- 9.4 Спецификации классов
- 9.5 Определения типов классов
Классы определяются с помощью небольшого языка, аналогичного языку модулей.
9.1 Типы классов
Типы классов являются классовыми аналогами выражений типов: они задают общую форму и свойства типов классов.
|
См. также следующие расширения языка: атрибуты и узлы расширения.
Простые выражения классов
Выражение путь-типа-класса эквивалентно типу класса, связанному с именем путь-типа-класса. Аналогично, выражение [ выражение-типа1 , … выражение-типаn ] путь-типа-класса эквивалентно параметрическому типу класса, связанному с именем путь-типа-класса, в котором типы параметров были соответственно заменены на выражение-типа1, …выражение-типаn.
Тип функции класса
Выражение типа класса выражение-типа -> тип-класса — это тип функций классов (функций от значений до классов), которые принимают в качестве аргумента значение типа выражение-типа и возвращают в качестве результата класс типа тип-класса.
Тип тела класса
Выражение типа класса object [( выражение-типа )] { спецификация-поля-класса } end — это тип тела класса. Он задаёт его переменные экземпляра и методы.
Тело класса будет соответствовать типу тела класса, если оно предоставляет определения для всех компонентов, указанных в типе тела класса, и эти определения удовлетворяют требованиям типа, заданным в типе тела класса. Кроме того, все методы, присутствующие в теле класса (виртуальные или публичные), должны также присутствовать в типе тела класса (с другой стороны, некоторые переменные экземпляра и конкретные закрытые методы могут быть опущены). Виртуальный метод будет соответствовать конкретному методу, что позволяет забыть его реализацию. Неизменяемая переменная экземпляра будет соответствовать изменяемой переменной экземпляра.
Локальные открытия
Локальные открытия поддерживаются в типах классов с OCaml 4.06.
Наследование
Конструктор наследования inherit тип-тела-класса обеспечивает включение методов и переменных экземпляра из других типов классов. Типы переменных экземпляра и методов из тип-тела-класса добавляются в текущий тип класса.
Спецификация переменных экземпляра
Спецификация переменной экземпляра записывается как val [mutable] [virtual] имя-переменной-экземпляра : выражение-типа, где имя-переменной-экземпляра — имя переменной экземпляра, а выражение-типа — её ожидаемый тип. Флаг 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 переопределял некоторые определения. Если такого переопределения не происходит, сигнализируется ошибка.
В качестве побочного эффекта, эти 3 ключевых слова избегают предупреждений 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 имя класса = тип тела класса определяет сокращение имя класса для типа тела класса тип тела класса. Как и в случае с определениями классов, также определены два сокращения типов имя класса и # имя класса. Определение может быть параметризовано некоторыми параметрами типа. Если любой метод в теле типа класса является виртуальным, определение должно быть помечено virtual.
Два определения типов классов совпадают, если у них одинаковые параметры типа и они расширяются до совпадающих типов.
© 1995-2024 INRIA.
https://ocaml.org/manual/5.2/classes.html