Почему лямбда-выражение Common-Lisp является допустимым именем функции?

Допустим, я хочу вызвать некоторую функцию. Если я определил функцию с defun, я просто использую имя функции в начале списка, за которым следуют ее аргументы, например (я буду использовать "=>" в примерах, чтобы показать результат ввода кода в CLisp REPL):

(defun f (a) (lambda (b) (+ a b))) => F
(f 12) => #<FUNCTION :LAMBDA (B) (+ A B)>

Достаточно просто. Первым элементом списка должно быть имя функции, специального оператора или макроса. Кроме того, также действительно следующее:

((lambda (a) (+ a 12)) 1) => 13

Быстрый поиск в Google показывает, что LAMBDA - это одновременно и символ, и макрос. Попытка увеличить урожай макроса:

(macroexpand '(lambda (a) (+ a 12))) => #'(LAMBDA (A) (+ A 12))

Это бесполезно. У меня нет возможности отличить макрос LAMBDA от символа LAMBDA, и я совершенно не понимаю, почему я могу использовать лямбда-выражение в качестве имени функции, но не, скажем, # 'f, что, насколько мне известно, должен оцениваться как действительный указатель функции для функции F таким же образом, как и # '(LAMBDA (A) (+ A 12)), но все же:

(#'f 12) => *** - EVAL: #'F is not a function name; try using a symbol instead

Является ли LAMBDA особым исключением из жестко установленного правила, согласно которому первый элемент вычисляемого выражения должен быть именем некоторой операции, или есть более последовательный набор правил, который я неправильно понимаю?


person Charlim    schedule 16.01.2019    source источник


Ответы (3)


Лямбда-выражения и названия функций

лямбда-выражение не является именем функции. Имена функций в Common Lisp определены как символы или (символ setf). Лямбда-выражение - это в основном встроенный синтаксис для описания анонимной функции.

Обратите внимание, что лямбда-выражения сами по себе не имеют смысла в Common Lisp. Они появляются только в лямбда-форме (см. Ниже) и внутри формы с помощью специального оператора function.

Списки как формы

Является ли LAMBDA особым исключением из жестко установленного правила, согласно которому первым элементом вычисляемого выражения должно быть имя некоторой операции, или есть более последовательный набор правил, который я неправильно понимаю?

Спецификация Common Lisp определяет, что существует только четыре формы на основе списков. Форма - это действительный фрагмент кода Лиспа.

  • специальные формы (форма начинается со специального оператора)
  • макро-формы (форма начинается с макрооператора)
  • функциональные формы (форма начинается с функционального оператора)
  • лямбда-формы (форма начинается с лямбда-выражения)

См. Common Lisp HyperSpec: Conses as Forms.

Обратите внимание, что в Common Lisp нет механизма для расширения этого. Есть только эти четыре типа форм на основе списков. Можно подумать о расширениях: массивы как функции, объекты CLOS как функции, различные типы функций, такие как fexprs, переменные, ... Ни один из них не поддерживается синтаксисом Common Lisp для форм на основе списков, и нет переносимого механизма для их добавления. .

ЛЯМБДА

LAMBDA имеет две разные цели в Common Lisp:

  • это заголовок лямбда-выражения.
  • как макрос LAMBDA. Это заменяет (lambda ....) на (function (lambda ....))

Макрос LAMBDA был добавлен в Common Lisp после первого определения языка CLtL1 для удобства, чтобы иметь возможность писать (lambda (x) x) вместо (function (lambda (x) x)) или #'(lambda (x) x). Таким образом, это сокращение от формы специального оператора функции и делает код более простым и более похожим на схему.

person Rainer Joswig    schedule 16.01.2019
comment
Кроме того, добавление LAMBDA в качестве макроса сделало ISLisp подмножеством Common Lisp. Неудивительно, что шоураннер ISLisp Кент Питман также предложил макросы LAMBDA. - person John Cowan; 25.01.2020

Я думаю, что проблема заключается в принтере lisp, реализация показывает нам одно и то же для обеих вещей, в то время как внутренне кажется, что на самом деле это список против функции.

Обычный и сбивающий с толку паттерн показывает:

<i>[hao@wendy:~]$ ecl
;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/init.lsp"
n;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-readline.fas"
ix-;;; Loading "/nix/store/j48bf40ssnzgil3qmdc759y0navk079d-ecl-readline/lib/ecl-completions.fas"
sECL (Embeddable Common-Lisp) 16.1.3 (git:UNKNOWN)
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2000 Juan J. Garcia-Ripoll
Copyright (C) 2016 Daniel Kochmanski
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.  
Top level in: #<process TOP-LEVEL>.
h+CL-USER[1]> (macroexpand '(lambda (a) (+a 12)))
#'(LAMBDA (A) (+A 12))
T
+CL-USER[2]> (equal (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))
NIL
+CL-USER[3]> (map 'list #'type-of (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(CONS CONS)
+CL-USER[4]> (map 'list #'functionp (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(NIL NIL)
+CL-USER[5]> (map 'list #'car (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
#'LAMBDA

И вроде разница только в hashquote?

+CL-USER[8]> (ignore-errors (list *print-escape* *print-readably* *print-case* *print-circle* *print-level* *print-length* *print-pretty*))
(T NIL :UPCASE NIL NIL NIL T)
+CL-USER[9]> (let ((*print-readably* t)) (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12)))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[10]> (setq *print-pretty* nil)
NIL
+CL-USER[11]> (setq the-funs (list (macroexpand '(lambda (a) (+ a 12))) '(lambda (a) (+ a 12))))
(#'(LAMBDA (A) (+ A 12)) (LAMBDA (A) (+ A 12)))
+CL-USER[12]> *print-pretty* 
NIL

Я пытался заставить принтер явно отображать (function (lambda ...)) часть определения [22], но я не могу вспомнить, когда в последний раз делал то же самое, пытаясь выяснить, почему тонкое проявление возникло именно тогда, когда начинало завоевывать доверие к lisp

+CL-USER[21]> (type-of (car the-funs)))
CONS
;;; Warning: Ignoring an unmatched right parenthesis.
+CL-USER[22]> (car (car the-funs))
FUNCTION

На самом деле мы видим список / минусов (ниже) и функцию (вверху)

+CL-USER[23]> (values #1=(cadr the-funs) (type-of #1#))
(LAMBDA (A) (+ A 12))
CONS

И, возможно, разница не только в визуальном напечатанном символе; Вот SBCL:

<i>[hao@wendy:~]$ sbcl
This is SBCL 2.0.0.nixos, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
* (setq fs (list (macroexpand '(lambda (x) (1+ x))) '(lambda (x) (1+ x))))
; in: SETQ FS
;     (SETQ FS (LIST (MACROEXPAND '(LAMBDA # #)) '(LAMBDA (X) (1+ X))))
; 
; caught WARNING:
;   undefined variable: COMMON-LISP-USER::FS
; 
; compilation unit finished
;   Undefined variable:
;     FS
;   caught 1 WARNING condition
(#'(LAMBDA (X) (1+ X)) (LAMBDA (X) (1+ X)))

* (let (*print-pretty*) (print (lambda (x) (1+ x))))

#<FUNCTION (LAMBDA (X)) {52B1CABB}> 
#<FUNCTION (LAMBDA (X)) {52B1CABB}>
* (let (*print-pretty*) (print ' (lambda (x) (1+ x))))

(LAMBDA (X) (1+ X)) 
(LAMBDA (X) (1+ X))

Цитата показана здесь более подробно

#<FUNCTION (LAMBDA (X)) {52B1CABB}> 
(LAMBDA (X) (1+ X))

Дерево и типы в чем-то похожи на то, что сказано в ECL.

* (mapcar #'functionp fs) 
(NIL NIL)
* (mapcar #'type-of fs)
(CONS CONS)
* (mapcar #'car fs)
#'LAMBDA
* (values (car (car fs)) (car (cdr fs)))
FUNCTION
(LAMBDA (X) (1+ X))

Наконец, (не предпринимайте) случайное блуждание по дереву через лес подтверждает, что мы можем пройтись по списку, но функция просто закрыта?

* (defun walk-tree (fun tree)
  (subst-if t
            (constantly nil)
            tree
            :key fun))
WALK-TREE
* (walk-tree 'print (lambda (x) (1+ x)))

#<FUNCTION (LAMBDA (X)) {52B1CD5B}> 
#<FUNCTION (LAMBDA (X)) {52B1CD5B}>
* (walk-tree 'print '(lambda (x) (1+ x))))

(LAMBDA (X) (1+ X)) 
LAMBDA 
((X) (1+ X)) 
(X) 
X 
NIL 
((1+ X)) 
(1+ X) 
1+ 
(X) 
X 
NIL 
NIL 
(LAMBDA (X) (1+ X))
* (defun walk-tree-atoms (fun tree)
  (tree-equal tree tree
              :test (lambda (element-1 element-2)
                      (declare (ignore element-2))
                      (funcall fun element-1)
                      t)))
WALK-TREE-ATOMS
* (walk-tree-atoms 'print (lambda (x) (1+ x)))

#<FUNCTION (LAMBDA (X)) {52B1CF9B}> 
T
* (walk-tree-atoms 'print '(lambda (x) (1+ x)))

LAMBDA 
X 
NIL 
1+ 
X 
NIL 
NIL 
T
* (quit)

<i>[hao@wendy:~]$ 

Функции ходьбы были взяты из LispTips, который, к сожалению, кажется, удален. У меня только что вспомнилось, когда я прочитал это, вы все еще можете посмотреть архив здесь: https://web.archive.org/web/20191204131626/https://lisptips.com/post/43404489000/the-tree-walkers-of-cl

Вы также можете просмотреть весь сеанс, я вставил его в суть: https://gist.github.com/LaloHao/fd6499b68cc98cf440aad6447ebd9b89

person Eduardo V.    schedule 11.10.2020

Можно представить себе следующий перевод, выполняемый для вычисления выражения функции (т. Е. Автомобиль не называет макрос или специальный оператор):

(foo arg1 arg2 ...)
~~~>
(funcall (function foo) arg1 arg2 ...)

И понимайте function как специальный оператор, который переводит то, что входит в первый элемент выражения, в фактический оператор функции, который можно вызвать. Это function, которое преобразует (в основном во время компиляции) лямбда-выражение в закрытие, которое может быть вызвано.

Наконец, обратите внимание, что #'foo - это сокращение от (function foo).

Это не то, как на самом деле все работает, на практике или в спецификации, поскольку (function #'(setf foo)) совершенно верно и может оценивать функцию, которая существует, но ((setf foo) arg1 arg2 ...) не может быть допустимым вызовом функции.

Однако это объясняет, почему такое выражение, как (#'f x), недействительно, и причина в том, что (function (function f)) не является допустимым выражением.

person Dan Robertson    schedule 17.01.2019
comment
Я думаю, что это немного вводит в заблуждение: он пытается понять Lisp-2 с помощью механизмов Lisp-1 - таких как получение функции и передача ее в пространстве значений вызывающему механизму. - person Rainer Joswig; 17.01.2019