Может ли Emacs Lisp назначить лямбда-форму такой переменной, как Scheme? - PullRequest
2 голосов
/ 13 февраля 2020

Исследуя ячейки символов 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, где лямбда-выражение является телом функции и присваивается имени переменной с универсальным значением 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), если я пытаюсь использовать его как функцию. Есть ли способ подражать миру Схемы в этом вопросе? Кажется, должен быть способ. Зачем еще в ячейке функции a содержится (lambda (&rest x) x), если мы не можем превратить это лямбда-выражение в функцию?

Ответы [ 3 ]

5 голосов
/ 13 февраля 2020

Важным отличием схемы от emacs lisp (да и от большинства других lisps) является то, что схема имеет одно пространство имен, тогда как в 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 состоит в том, что можно задавать переменные, имя которых вы не знаете, а коммутируете.

3 голосов
/ 13 февраля 2020

Это дополнение к другому ответу . Другой ответ объясняет разницу между lisp-1 (lisps, которые имеют одно пространство имен для привязок функций и переменных) и lisp-2s (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 ...) is fine 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...))

И я думаю, что это нормально: в Схеме эквивалент будет ужасным.

Так что это одна из причин, почему 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) не делает то, что ожидает ! Это катастрофа: это означает, что если я связываю имя функции - любую функцию, включая функции, о которых я, возможно, не знаю, - как переменную, то любой код в области действия Dynami c этой привязки не будет делать то, что он should.

Таким образом, для Lisp с динамической областью действия c, как исторически было и есть у elisp по умолчанию, быть lisp-1 было катастрофой. Исторически сложилось так, что очень многие реализации lisp имели динамическую область действия c (по крайней мере, в интерпретируемом коде: для скомпилированного кода было характерно иметь разные правила области видимости, а правила области видимости часто были несколько непоследовательными в общем). Так что для тех реализаций, быть lisp-2 было действительно значительным преимуществом. И, разумеется, когда существовал большой код, который предполагал lisp-2-ness, языкам, нацеленным на совместимость, таким как CL, было намного легче оставаться lisp-2s, даже несмотря на преимущества в языке с лексической областью действия менее ясны.


Как примечание: я использовал lisp, long a go, который был динамически ограничен (по крайней мере в интерпретаторе?) и lisp-1. И у меня был, по крайней мере, один очень плохой опыт (я думаю, что мне потребовалось жестко перезагрузить многопользовательский компьютер, который стал catatoni c, потому что он так много пейджировал, что сделало меня непопулярным среди всех остальных пользователей) в результате этого .

1 голос
/ 27 марта 2020

Существует два способа описания языка: еще один абстрактный и еще один конкретный c.

Один из них иллюстрируется тем, что на схеме

(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 s «функциональная ячейка» .

Если после этого мы вызовем (+ 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 в качестве аргумента
...