Это дополнение к другому ответу . Другой ответ объясняет разницу между 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, потому что он так много пейджировал, что сделало меня непопулярным среди всех остальных пользователей) в результате этого .