Может ли 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 как аргумент
Другие вопросы по тегам