13.14 Встроенные функции

Встроенная функция — это функция, которая работает как обычная функция, за исключением одного: когда вы байт-компилируете вызов функции (см. Байт-компиляция), определение функции расширяется в вызывающий код.

Простой способ определения встроенной функции — использовать defsubst вместо defun. Остальная часть определения выглядит так же, но использование defsubst указывает, что она должна быть встроенной для байт-компиляции.

Макрос: defsubst name args [doc] [declare] [interactive] body…

Этот макрос определяет встроенную функцию. Его синтаксис в точности такой же, как у defun (см. Определение функций).

Превращение функции во встроенную часто ускоряет ее вызовы. Но у нее также есть недостатки. Во-первых, это снижает гибкость; если вы измените определение функции, вызовы, которые уже были встроены, продолжат использовать старое определение до тех пор, пока вы их не перекомпилируете.

Другой недостаток заключается в том, что превращение большой функции во встроенную может увеличить размер скомпилированного кода как в файлах, так и в памяти. Поскольку преимущество в скорости встроенных функций наибольшее для небольших функций, как правило, не следует делать большие функции встроенными.

Кроме того, встроенные функции плохо работают с точки зрения отладки, трассировки и консультирования (см. Консультирование функций Emacs Lisp). Поскольку простота отладки и гибкость переопределения функций являются важными функциями Emacs, вы не должны делать функцию встроенной, даже если она мала, если только ее скорость не является действительно критичной, и вы не измерили код, чтобы убедиться, что использование defun действительно вызывает проблемы с производительностью.

После определения встроенной функции ее встроенное расширение может быть выполнено позже в том же файле, подобно макросам.

Возможно использовать defmacro для определения макроса, который расширяется в тот же код, который выполнила бы встроенная функция (см. Макросы). Но макрос будет ограничен прямым использованием в выражениях — макрос не может быть вызван с помощью apply, mapcar и так далее. Кроме того, для преобразования обычной функции в макрос требуется некоторая работа. Преобразование ее во встроенную функцию легко; просто замените defun на defsubst. Поскольку каждый аргумент встроенной функции вычисляется ровно один раз, вам не нужно беспокоиться о том, сколько раз тело использует аргументы, как это происходит с макросами.

В качестве альтернативы вы можете определить функцию, предоставив код, который будет ее встраивать как компиляторный макрос (см. Форма declare). Следующие макросы делают это возможным.

Макрос: define-inline name args [doc] [declare] body…

Определяет функцию name, предоставляя код, который выполняет ее встраивание, как компиляторный макрос. Функция примет список аргументов args и будет иметь указанное body.

Если присутствует, doc должен быть строкой документации функции (см. Строки документации функций); declare, если присутствует, должен быть формой declare (см. Форма declare), указывающей метаданные функции.

Функции, определенные с помощью define-inline, имеют несколько преимуществ по сравнению с макросами, определенными с помощью defsubst или defmacro:

Как и defmacro, функция, встроенная с помощью define-inline, наследует правила области видимости, динамические или лексические, от места вызова. См. Правила области видимости для привязок переменных.

Следующие макросы следует использовать в теле функции, определенной с помощью define-inline.

Макрос: inline-quote expression

Квотирует expression для define-inline. Это похоже на обратную кавычку (см. Обратная кавычка), но квотирует код и принимает только ,, а не ,@.

Макрос: inline-letevals (bindings…) body…

Это предоставляет удобный способ гарантировать, что аргументы встроенной функции вычисляются ровно один раз, а также создавать локальные переменные.

Это похоже на let (см. Локальные переменные): он устанавливает локальные переменные, указанные в bindings, а затем вычисляет body с этими привязками.

Каждый элемент bindings должен быть либо символом, либо списком в форме (var expr); результатом является вычисление expr и привязка var к результату. Однако, когда элемент bindings является просто символом var, результат вычисления var перепривязывается к var (что сильно отличается от способа работы let).

Хвост bindings может быть либо nil, либо символом, который должен содержать список аргументов, в этом случае каждый аргумент вычисляется, и символ привязывается к полученному списку.

Макрос: inline-const-p expression

Возвращает не-nil, если значение expression уже известно.

Макрос: inline-const-val expression

Возвращает значение expression.

Макрос: inline-error format &rest args

Сигнализирует об ошибке, форматируя args в соответствии с format.

Вот пример использования define-inline:

(define-inline myaccessor (obj)
  (inline-letevals (obj)
    (inline-quote (if (foo-p ,obj) (aref (cdr ,obj) 3) (aref ,obj 2)))))

Это эквивалентно

(defsubst myaccessor (obj)
  (if (foo-p obj) (aref (cdr obj) 3) (aref obj 2)))