Мне все еще немного непонятно, в чем вы смущены, но вот попытка объяснить это. Я буду придерживаться CL (и в основном ANSI CL), потому что у elisp есть много исторических странностей, которые только усложняют понимание (на elisp есть приложение). До ANSI CL также было гораздо менее понятно о различных вещах.
Я попытаюсь объяснить это с помощью написания макроса, который является простой версией defun
: Назовите это defun/simple
, и пример его использования будет
(defun/simple foo (x)
(+ x x))
. Поэтому мне нужно выяснить, каким должно быть расширение этого макроса, чтобы он делал что-то в целом эквивалентное ( но проще, чем) defun
.
Пространство имен функции & fdefinition
Прежде всего, я предполагаю, что вас устраивает мысль о том, что в CL (и elisp) пространство имен функций отличается от пространства имен привязок переменных: оба языка - это lisp-2. Таким образом, в форме, подобной (f x)
, f
ищется в пространстве имен привязок функций, тогда как x
ищется в пространстве имен привязок переменных. Это означает, что формы, подобные
(let ((sin 0.0))
(sin sin))
, подходят для CL или elisp, тогда как в Scheme они будут ошибкой, поскольку 0.0
не является функцией, поскольку Scheme представляет собой lisp-1.
Итак, нам нужен какой-то способ доступа к этому пространству имен, а в CL самый общий способ сделать это - fdefinition
: (fdefinition <function name>)
получает определение функции <function name>
, где <function name>
это то, что называет функцию, которая для наших целей будет символом.
fdefinition
- это то, что CL называет аксессором : это означает, что макрос setf
знает, что с ним делать, так что мы можем изменить привязку функции символа на (setf (fdefinition ...) ...)
. (Это неправда: мы можем получить доступ и изменить с помощью fdefinition
привязку символа верхнего уровня символа, мы не можем получить доступ или изменить привязки лексической функции, и CL не предоставляет никакого способа сделать это, но здесь это не имеет значения.)
Так что это говорит нам о том, как должно выглядеть наше расширение макроса: мы хотим установить определение (верхнего уровня) имени к некоторому объекту функции. Расширение макроса должно выглядеть следующим образом:
(defun/simple foo (x)
x)
должно расширяться до чего-то, включающего
(setf (fdefinition 'foo) <form which makes a function>)
Так что мы можем написать этот бит макроса сейчас:
(defmacro defun/simple (name arglist &body forms)
`(progn
(setf (fdefinition ',name)
,(make-function-form name arglist forms))
',name))
Это полное определение этого макроса. Он использует progn
в своем расширении, так что результатом расширения является имя определяемой функции, которое совпадает с defun
: расширение выполняет всю свою реальную работу с побочным эффектом.
Но defun/simple
опирается на вспомогательную функцию, называемую make-function-form
, которую я еще не определил, поэтому вы на самом деле не можете использовать пока.
Формы функций
Итак, теперь нам нужно написать make-function-form
. Эта функция вызывается во время макроразвлечения: ее задача не состоит в том, чтобы создать функцию: она возвращает немного исходного кода , который создаст функцию, которую я называю «формой функции».
Итак, как выглядят формы функций в CL? Ну, на самом деле в переносимом CL есть только одна такая форма (это может быть неправильно, но я думаю, что это правда), это форма, созданная с помощью специального оператора function
. Поэтому нам нужно будет вернуть некоторую форму, которая выглядит как (function ...)
. Ну, что может быть ...
? Существует два случая, когда function
.
(function <name>)
обозначает функцию, названную <name>
в текущей лексической среде. Так что (function car)
- это функция, которую мы вызываем, когда говорим (car x)
. (function (lambda ...))
обозначает функцию, указанную (lambda ...)
: лямбда-выражением .
Вторым из них является only (предостережения, как указано выше), с помощью которого мы можем создать форму, обозначающую новую функцию. Так что make-function-form
потребуется вернуть второй вариант формы function
.
Итак, мы можем написать начальную версию make-function-form
:
(defun make-function-form (name arglist forms)
(declare (ignore name))
`(function (lambda ,arglist ,@forms)))
И этого достаточно для defun/simple
на работу:
> (defun/simple plus/2 (a b)
(+ a b))
plus/2
> (plus/2 1 2)
3
Но это еще не совсем правильно: одна из вещей, которые могут сделать функции, определенные defun
, - это возврат от себя: они знают свое имя и могут использовать return-from
, чтобы вернуться из него:
> (defun silly (x)
(return-from silly 3)
(explode-the-world x))
silly
> (silly 'yes)
3
defun/simple
пока не может этого сделать. Для этого make-function-form
необходимо вставить подходящий block
вокруг тела функции:
(defun make-function-form (name arglist forms)
`(function (lambda ,arglist
(block ,name
,@forms))))
А теперь:
> (defun/simple silly (x)
(return-from silly 3)
(explode-the-world x))
silly
> (silly 'yes)
3
И все хорошо.
Это окончательное определение defun/simple
и его вспомогательной функции.
Глядя на расширение defun/simple
Мы можем сделать это с помощью macroexpand
обычным способом :
> (macroexpand '(defun/simple foo (x) x))
(progn
(setf (fdefinition 'foo)
#'(lambda (x)
(block foo
x)))
'foo)
t
Единственное, что здесь сбивает с толку, это то, что, поскольку (function ...)
распространен в исходном коде, есть синтаксис c сахар для него, который #'...
: это та же самая причина, что quote
имеет специальный синтаксис.
Стоит посмотреть на макроразложение реальных defun
форм: в них обычно есть куча специфических для реализации c вещей, но вы можете найти то же самое там. Вот пример из LW:
> (macroexpand '(defun foo (x) x))
(compiler-let ((dspec::*location* '(:inside (defun foo) :listener)))
(compiler::top-level-form-name (defun foo)
(dspec:install-defun 'foo
(dspec:location)
#'(lambda (x)
(declare (system::source-level
#<eq Hash Table{0} 42101FCD5B>))
(declare (lambda-name foo))
x))))
t
Ну, здесь есть много лишних вещей, и у LW, очевидно, есть некоторая хитрость в этой форме (declare (lambda-name ...))
, которая позволяет return-from
работать без явного блока. Но вы можете видеть, что в основном происходит то же самое.
Вывод: как вы создаете функции
В заключение: нужен макрос типа defun
или любая другая форма, определяющая функции развернуть в форму, которая при оценке создаст функцию. CL предлагает ровно одну такую форму: (function (lambda ...))
: - так вы создаете функции в CL . Поэтому что-то вроде defun
обязательно должно расширяться до чего-то подобного. (Если быть точным: любая переносимая версия defun
: реализации могут быть свободны для реализации-magi c и могут делать это. Однако они не свободны для добавления нового специального оператора.)
Что вы видите, когда вызываете describe
, так это то, что после того, как SBCL скомпилировал вашу функцию, он вспомнил, какой была исходная форма, и исходная форма была именно той, которую вы получили бы из defun/simple
здесь указан макрос.
Примечания
lambda
как макрос
В ANSI CL lambda
определяется как макрос, расширение которого подходит (function (lambda ...))
форма:
> (macroexpand '(lambda (x) x))
#'(lambda (x) x)
t
> (car (macroexpand '(lambda (x) x)))
function
Это означает, что вам не нужно писать (function (lambda ...))
самостоятельно: вы можете положиться на макроопределение lambda
, делающее это за вас. Исторически, lambda
не всегда был макросом в CL: я не могу найти свою копию CLtL1, но я почти уверен, что она там не была определена. Я вполне уверен, что определение макроса lambda
появилось так, что было возможно писать ISLisp-совместимые программы поверх CL. Это должно быть на языке, потому что lambda
находится в пакете CL
, и поэтому пользователи не могут переносить макросы для него (хотя довольно часто они определяют такой макрос, или, по крайней мере, я). Я не использовал приведенное выше определение макроса.
defun/simple
не претендует на то, чтобы быть правильным клоном defun
: его единственная цель - показать, как можно написать такой макрос. В частности, это не касается объявлений должным образом, я думаю: они должны быть удалены из block
и не являются.
Elisp
Elisp намного ужаснее, чем CL. В частности, в CL есть четко определенный тип function
, который не пересекается со списками:
> (typep '(lambda ()) 'function)
nil
> (typep '(lambda ()) 'list)
t
> (typep (function (lambda ())) 'function)
t
> (typep (function (lambda ())) 'list)
nil
(Обратите внимание, в частности, что (function (lambda ()))
является функцией, а не списком: function
выполняет свою функцию по созданию функции.)
В elisp, однако, интерпретируемая функция - это просто список, машина которого равна lambda
(предостережение: если лексическая привязка включена, это не тот случай: тогда это список, чья машина closure
). Таким образом, в elisp (без лексической привязки):
ELISP> (function (lambda (x) x))
(lambda (x)
x)
И
ELISP> (defun foo (x) x)
foo
ELISP> (symbol-function 'foo)
(lambda (x)
x)
Затем интерпретатор elisp просто интерпретирует этот список, так же, как вы могли бы сами. function
в elisp - это почти то же самое, что quote
.
Но function
не совсем то же самое, что и quote
в elisp: байт-компилятор знает, что когда он сталкивается с формой, подобной (function (lambda ...))
, это - форма функции, и он должен байтово-компилироваться тело. Итак, мы можем посмотреть на расширение defun
в elisp:
ELISP> (macroexpand '(defun foo (x) x))
(defalias 'foo
#'(lambda (x)
x))
(Оказывается, defalias
сейчас является примитивной вещью.)
Но если я поставлю это определение в файле, который я байт компилирую и загружаю, затем:
ELISP> (symbol-function 'foo)
#[(x)
"\207"
[x]
1]
И вы можете исследовать это немного дальше: если вы поместите this в файл:
(fset 'foo '(lambda (x) x))
и затем байтовая компиляция и загрузка, затем
ELISP> (symbol-function 'foo)
(lambda (x)
x)
Таким образом, байтовый компилятор ничего не сделал с foo
, потому что он не получил подсказку, что он должен. Но foo
все еще хорошая функция:
ELISP> (foo 1)
1 (#o1, #x1, ?\C-a)
Она просто не скомпилирована. Вот почему, если вы пишете код elisp с анонимными функциями, вы должны использовать function
(или эквивалентно #'
). (И, наконец, конечно, (function ...)
правильно делает, если лексическая область видимости включена.)
Другие способы создания функций в CL
Наконец, я уже говорил выше, что function
& конкретно (function (lambda ...))
- единственный примитивный способ создания новых функций в CL. Я не совсем уверен, что это правда, особенно учитывая CLOS (почти любой CLOS будет иметь своего рода экземпляры классов, которые являются функциями, но которые могут быть разделены на подклассы). Но это не имеет значения: это способ, и этого достаточно.