Может ли Emacs Lisp назначить лямбда-форму такой переменной, как Scheme?
Изучая ячейки символов Emacs Lisp, я обнаружил, что для примера функции вроде
(defun a (&rest x)
x)
я могу позвонить (symbol-function 'a)
, который возвращает (lambda (&rest x) x)
. Я могу использовать его, если захочу
> ((lambda (&rest x) x) 1 2 3 4 5)
(1 2 3 4 5)
который имеет те же функции, что и исходная функция выше. Это напоминает мне Scheme, где лямбда-выражение является телом функции и назначается имени переменной с помощью универсальногоdefine
. Например
(define atom?
(lambda (x)
(and (not (pair? x)) (not (null? x)))))
просто присваивает лямбда-выражение atom?
-- и сейчас atom?
это функция. Так может ли elisp сделать это, т. Е. Назначить лямбда-выражение символу, а затем использовать его как функцию? я пытался
(setq new-a (lambda (&rest x) x))
который дает (void-function new-a)
если я попытаюсь использовать его как функцию. Есть ли способ подражать миру Scheme в этом вопросе? Похоже, должен быть способ. Зачем еще функциональная ячейкаa
содержать (lambda (&rest x) x)
если бы мы не могли превратить это лямбда-выражение в функцию?
3 ответа
Важное различие между схемой и emacs lisp (и действительно большинством других lisp) заключается в том, что схема имеет одно пространство имен, тогда как emacs lisp имеет отдельные пространства имен для функций и переменных. Первая позиция в форме списка, которая оценивается, называет функцию, и это имя ищется в пространстве имен функции. В схеме все имена находятся в одном пространстве, значение, связанное с именем, ищется и используется везде, где оно появляется.
Это означает, что в emacs lisp вы можете что-то вроде этого:
(defun f (x) (+ x x))
(setq f 2)
(f f) ;=> 4
В схеме это невозможно, здесь будет только один f
и если вы установите его значение, оно изменится с (скажем) функции на число.
В emacs lisp есть разные способы справиться с этим.
Один из них - использовать такие функции, как funcall
а также apply
, они принимают функцию и некоторые аргументы и применяют функцию к аргументам, как в:
(setq f (lambda (x) (+ x x)))
(funcall f 2) ;=> 4
Другой подход - манипулировать тем, что имя функции f
средства. Есть функция под названиемfset
который позволяет вам прикреплять функции к именам (в пространстве имен функций):
(fset 'f (lambda (x) (+ x x x)))
(f 2) ;=> 6
Обратите внимание, что fset
работает с именами (также известными как символы), поэтому имя f
необходимо заключить в кавычки, иначе оно будет считаться значением переменной. Вот почему функция переменной называетсяsetq
, "q" означает "цитируемый", поэтому setq
на самом деле это специальная функция, которая цитирует свой первый аргумент, так что программисту не нужно это делать. Существует эквивалентная нормальная функция, называемаяset
в котором не используется цитирование, например:
(setq x 1) ; x is 1
(set 'x 2) ; x is 2
(setq x 'x) ; x is the symbol x
(set x 3) ; x is now 3
Последняя форма может показаться запутанной, но как set
это нормальная форма, она будет искать значение переменной x
, это значение - символ x
и затем именует переменную, которая будет изменена (т.е. x
). Таким образом, одно преимуществоset
заключается в том, что можно установить переменные, имя которых вы не знаете, а скорее коммутирует.
Это дополнение к другому ответу. Другой ответ объясняет разницу между lisp-1 (lisp, которые имеют одно пространство имен для привязок функций и переменных) и lisp-2 (lisp, у которых есть отдельное пространство имен для привязок функций).
Я хочу объяснить, почему lisp-2 может улучшить ситуацию, и особенно почему это произошло исторически.
Прежде всего, давайте рассмотрим немного кода схемы:
(define (foo x)
(let ([car (car x)])
... in here (car ...) is probably not going to get the car
(bar car)))
(define (bar thing)
... but in here, car is what you expect ...)
Итак, в foo
Я связал car
к машине аргумента. Вероятно, это ужасный стиль в Scheme, и это означает, что в теле этой привязкиcar
вероятно, не делает того, что вы ожидаете при использовании в качестве функции. Но эта проблема имеет значение только в пределах лексической области привязкиcar
: не имеет значения внутриbar
например.
Теперь в Common Lisp я могу написать эквивалентный код:
(defun foo (x)
(let ((car (car x)))
... (car ...) isfine in here ...
(bar car)))
(defun bar (thing)
... and here ...)
Так что, возможно, это немного лучше: внутри тела привязки car
все еще нормально использовать car
как функция, и действительно, компилятор может сделать очень сильные предположения, что car
- это функция, определяемая языком, а в стандарте CL есть формулировка, которая гарантирует, что это всегда верно.
А это значит, что стилистически в CL что-то вроде этого, наверное, нормально. В частности, я часто делаю такие вещи, как:
(defmethod manipulate-thing ((thing cons))
(destructuring-bind (car . cdr) thing
...use car & cdr...))
И я думаю, что это нормально: в Scheme эквивалент был бы ужасен.
Это одна из причин, почему lisp-2 довольно удобен. Однако есть гораздо более сильный, который не распространяется на CL, но это применяется к Elisp.
Рассмотрим в elisp этот код:
(defun foo (x)
(let ((car (car x))
(cdr (cdr x)))
(bar car cdr)))
(defun bar (thing-1 thing-2)
...)
Теперь о elisp нужно знать очень важную вещь: по умолчанию он имеет динамическую область видимости. Это значит, что когдаbar
вызывается из foo
, привязки car
а также car
видны в bar
.
Так, например, если я переопределю bar
в качестве:
(defun bar (thing-1 thing-2)
(cons cdr thing-1))
Затем:
ELISP> (foo '(1 . 2))
(2 . 1)
Итак, теперь подумайте, что произошло бы, если бы elisp был lisp-1: любая функция, вызываемая изfoo
обнаружит, что (car x)
не делает то, что ожидает! Это катастрофа: это означает, что если я привяжу имя функции - любую функцию, включая функции, о существовании которых я мог не знать, - как переменную, то любой код в динамической области этой привязки не будет делать то, что должен.
Итак, для Lisp с динамической областью видимости, как у elisp исторически было и есть по умолчанию, использование lisp-1 было катастрофой. Исторически сложилось так, что очень многие реализации Lisp действительно имели динамическую область видимости (по крайней мере, в интерпретируемом коде: для скомпилированного кода были характерны разные правила области видимости, а правила области видимости часто были в целом несколько несогласованными). Так что для этих реализаций наличие lisp-2 было действительно значительным преимуществом. И, конечно же, когда существовал большой объем кода, который предполагал наличие лиспа-2, языкам, нацеленным на совместимость, таким как CL, было намного проще оставаться Lisp-2, даже несмотря на то, что преимущества в языке с лексической областью видимости менее ясны.
В качестве примечания: я давно использовал lisp, который был как с динамической областью видимости (по крайней мере, в интерпретаторе?), Так и с lisp-1. И у меня был по крайней мере один очень плохой опыт (я думаю, связанный с необходимостью полной перезагрузки многопользовательской машины, которая стала кататонической из-за того, что она так много пейджировала, что сделало меня непопулярным среди всех других пользователей) в результате этого.
Существует два способа описания языка: один более абстрактный, а другой более конкретный.
Один из них проиллюстрирован тем, что на схеме
(define (f x) (+ x x x))
вызывает оценку
(f y)
быть таким же, как оценка
((lambda (x) (+ x x x)) y)
быть таким же, как оценка
(let ((x y)) (+ x x x))
быть таким же, как оценка
(+ y y y)
Обратите внимание, мы ничего не сказали о том, как все это реализовано.
Другой способ - обратиться к специфике конкретной реализации в машине.
Таким образом, для Common Lisp / Emacs Lisp мы начинаем с разговора о реальных объектах памяти в системе времени выполнения языка, называемых символами.
У символа есть то и это - это как структура с несколькими полями, которые можно заполнить какими-то данными или оставить пустыми. Представление символа в памяти, фактическая структура в памяти, имеет поле, называемое "ячейка переменной", и поле, называемое "ячейка функции", и что у вас есть.
Когда мы звоним (fset 'f (lambda (x) (+ x x x)))
, мы сохраняем результат вычисления (lambda (x) (+ x x x))
форма в символе F
" Функциональная ячейка".
Если мы позвоним (+ f 2)
после того, F
" ячейка переменной" исследуется, чтобы узнать ее значение как переменной, что вызывает ошибку "неопределенная переменная".
Если мы позвоним (f 2)
, F
" функциональная ячейка" исследуется, чтобы узнать ее значение как функции (вот что(symbol-function 'f)
тоже делает). Оказывается, удерживает результат оценки(lambda (x) (+ x x x))
, поэтому вызов функции эквивалентен ((lambda (x) (+ x x x)) 2)
сделан.
edit: И если вы хотите вызвать функцию, хранящуюся в " ячейкепеременной " символа, как функцию, вам нужно использоватьfuncall
, который обращается к значению символа как переменной и использует его как функцию. В Common Lisp (CLISP), другом языке Lisp-2:
[14]> (setq a (lambda (x) (+ x x x)))
#<FUNCTION :LAMBDA (X) (+ X X X)>
[15]> (funcall a 3)
9
[16]> (symbol-value 'a)
#<FUNCTION :LAMBDA (X) (+ X X X)>
[17]> (let ((x (symbol-value 'a))) (funcall x 3))
9
[18]> (let ((x 1)) (setf (symbol-function 'x) (symbol-value 'a)) (x 3))
9
setf
примитив "установленного места" Common Lisp(setq a <val>)
такой же как(setf (symbol-value 'a) <val>)
symbol-value
обращается к ячейке переменной символа (ее значению как переменной)symbol-function
обращается к функциональной ячейке символа (ее значению как функции)(funcall x 3)
получает(symbol-value 'x)
и вызывает результат с помощью3
как аргумент(x 3)
получает(symbol-function 'x)
и вызывает результат с помощью3
как аргумент