Функции, определённые с помощью defun, имеют жёстко заданный набор предположений о типах и ожидаемых значениях своих аргументов. Например, функция, спроектированная для обработки значений своего аргумента, которые являются либо числами, либо списками чисел, завершится ошибкой или выдаст сигнал об ошибке, если её вызвать со значением любого другого типа, например, вектором или строкой. Это происходит потому, что реализация функции не готова обрабатывать типы, отличные от тех, которые предполагались при проектировании.
В отличие от этого, объектно-ориентированные программы используют полиморфные функции: набор специализированных функций с одинаковым именем, каждая из которых написана для определённого набора типов аргументов. Какая из функций будет фактически вызвана, определяется во время выполнения на основе типов фактических аргументов.
Emacs предоставляет поддержку полиморфизма. Как и другие Lisp-среды, в частности Common Lisp и его система объектов Common Lisp Object System (CLOS), эта поддержка основана на обобщённых функциях. Обобщённые функции Emacs строго следуют CLOS, включая использование схожих имён, поэтому если у вас есть опыт работы с CLOS, остальная часть этого раздела покажется вам очень знакомой.
Обобщённая функция определяет абстрактную операцию, задавая её имя и список аргументов, но (обычно) не предоставляя реализации. Фактическая реализация для нескольких конкретных классов аргументов обеспечивается методами, которые должны определяться отдельно. Каждый метод, реализующий обобщённую функцию, имеет то же имя, что и обобщённая функция, но определение метода указывает, какие виды аргументов он может обрабатывать, путём специализации аргументов, определённых в обобщённой функции. Эти специализаторы аргументов могут быть более или менее конкретными; например, тип string является более конкретным, чем более общий тип, такой как sequence.
Обратите внимание, что, в отличие от основанных на сообщениях ОО-языков, таких как C++ и Simula, методы, реализующие обобщённые функции, не принадлежат классу, они принадлежат обобщённой функции, которую они реализуют.
Когда вызывается обобщённая функция, она выбирает применимые методы, сравнивая фактические аргументы, переданные вызывающим кодом, со специализаторами аргументов каждого метода. Метод является применимым, если фактические аргументы вызова совместимы со специализаторами метода. Если применимо более одного метода, они комбинируются с использованием определённых правил, описанных ниже, и затем комбинация обрабатывает вызов.
Этот макрос определяет обобщённую функцию с указанными именем и аргументами. Если присутствует тело, оно предоставляет реализацию по умолчанию. Если присутствует документация (она должна присутствовать всегда), она задаёт строку документации для обобщённой функции в форме (:documentation строка-документации). Необязательные параметры-и-методы могут быть одной из следующих форм:
(declare объявления)Форма declare, как описано в Форма declare.
(:argument-precedence-order &rest аргументы)Эта форма влияет на порядок сортировки при комбинировании применимых методов. Обычно, когда при комбинировании сравниваются два метода, аргументы метода рассматриваются слева направо, и первый метод, чей специализатор аргумента является более конкретным, будет предшествовать другому. Порядок, определённый этой формой, переопределяет это, и аргументы рассматриваются в соответствии с их порядком в этой форме, а не слева направо.
(:method [квалификаторы…] аргументы &rest тело)Эта форма определяет метод аналогично тому, как это делает cl-defmethod.
Этот макрос определяет конкретную реализацию для обобщённой функции с именем имя. Код реализации задаётся телом. Если присутствует, строка-документации является строкой документации для метода. Список аргументов, который должен быть идентичным во всех методах, реализующих обобщённую функцию, и должен соответствовать списку аргументов этой функции, предоставляет специализаторы аргументов вида (арг спец), где арг — это имя аргумента, указанное в вызове cl-defgeneric, а спец — одна из следующих форм специализаторов:
типЭтот специализатор требует, чтобы аргумент был заданного типа, одного из типов иерархии типов, описанной ниже.
(eql объект)Этот специализатор требует, чтобы аргумент был eql заданному объекту.
(head объект)Аргумент должен быть cons-ячейкой, чей car является eql объекту.
тип-структурыАргумент должен быть экземпляром класса с именем тип-структуры, определённого с помощью cl-defstruct (см. Структуры в Расширения Common Lisp для GNU Emacs Lisp), или одного из его дочерних классов.
Определения методов могут использовать новое ключевое слово в списке аргументов, &context, которое вводит дополнительные специализаторы, проверяющие окружение во время выполнения метода. Это ключевое слово должно появляться после списка обязательных аргументов, но перед любыми ключевыми словами &rest или &optional. Специализаторы &context выглядят очень похоже на обычные специализаторы аргументов — (выражение спец) — за исключением того, что выражение — это выражение, вычисляемое в текущем контексте, а спец — это значение для сравнения. Например, &context (overwrite-mode (eql t)) сделает метод применимым только тогда, когда включён overwrite-mode. За ключевым словом &context может следовать любое количество контекстных специализаторов. Поскольку контекстные специализаторы не являются частью сигнатуры аргументов обобщённой функции, они могут быть опущены в методах, которые их не требуют.
Специализатор типа, (арг тип), может указывать один из системных типов в следующем списке. Когда указан родительский тип, аргумент, тип которого является любым из его более конкретных дочерних типов, а также внучатых, правнучатых и т. д., также будет совместим.
integerРодительский тип: number.
numbernullРодительский тип: symbol
symbolstringРодительский тип: array.
arrayРодительский тип: sequence.
consРодительский тип: list.
listРодительский тип: sequence.
markeroverlayfloatРодительский тип: number.
window-configurationprocesswindowsubrcompiled-functionbufferchar-tableРодительский тип: array.
bool-vectorРодительский тип: array.
vectorРодительский тип: array.
framehash-tablefont-specfont-entityfont-objectНеобязательный элемент дополнительно, выражаемый как ‘:extra строка’, позволяет вам добавлять больше методов, различаемых по строке, для одних и тех же специализаторов и квалификаторов.
Необязательный квалификатор позволяет комбинировать несколько применимых методов. Если он отсутствует, определённый метод является основным методом, отвечающим за предоставление основной реализации обобщённой функции для специализированных аргументов. Вы также можете определять вспомогательные методы, используя одно из следующих значений в качестве квалификатора:
:beforeЭтот вспомогательный метод будет выполняться перед основным методом. Точнее, все методы :before будут выполняться перед основным, в порядке от наиболее конкретного к наименее конкретному.
:afterЭтот вспомогательный метод будет выполняться после основного метода. Точнее, все такие методы будут выполняться после основного, в порядке от наименее конкретного к наиболее конкретному.
:aroundЭтот вспомогательный метод будет выполняться вместо основного метода. Наиболее конкретный из таких методов будет выполнен перед любым другим методом. Такие методы обычно используют cl-call-next-method, описанный ниже, для вызова других вспомогательных или основных методов.
Функции, определённые с помощью cl-defmethod, не могут быть интерактивными, то есть командами (см. Определение команд), путём добавления к ним формы interactive. Если вам нужна полиморфная команда, мы рекомендуем определить обычную команду, которая вызывает полиморфную функцию, определённую через cl-defgeneric и cl-defmethod.
При каждом вызове обобщённой функции она строит эффективный метод, который будет обрабатывать этот вызов, комбинируя применимые методы, определённые для функции. Процесс поиска применимых методов и создания эффективного метода называется диспетчеризацией. Применимыми являются те методы, все специализаторы которых совместимы с фактическими аргументами вызова. Поскольку все аргументы должны быть совместимы со специализаторами, все они определяют, является ли метод применимым. Методы, которые явно специализируют более одного аргумента, называются методами с множественной диспетчеризацией.
Применимые методы сортируются в порядке их комбинирования. Метод, чей самый левый специализатор аргумента является наиболее конкретным, будет первым в порядке. (Указание :argument-precedence-order в составе cl-defmethod переопределяет это, как описано выше.) Если тело метода вызывает cl-call-next-method, будет выполнен следующий по конкретности метод. Если есть применимые методы :around, наиболее конкретный из них будет выполнен первым; он должен вызвать cl-call-next-method для выполнения любых менее конкретных методов :around. Затем методы :before выполняются в порядке их конкретности, за ними следует основной метод, и, наконец, методы :after в обратном порядке их конкретности.
При вызове из лексического тела основного или вспомогательного метода :around вызывает следующий применимый метод для той же обобщённой функции. Обычно он вызывается без аргументов, что означает вызов следующего применимого метода с теми же аргументами, с которыми был вызван вызывающий метод. В противном случае используются указанные аргументы.
Эта функция при вызове из лексического тела основного или вспомогательного метода :around возвращает не nil, если существует следующий метод для вызова.