Использование лямбда-значения из функции в качестве первого элемента списка
Я читаю "Парадигмы программирования искусственного интеллекта" Питера Норвига и столкнулся с проблемой, которую не могу решить самостоятельно (это мое введение в Лисп). На самом деле проблема довольно маленькая, но, очевидно, не та, которую мой маленький мозг может решить.
Почему, когда значение функции является лямбда-выражением, ошибочно использовать эту функцию в качестве первого элемента списка. Например:
Lisp:
(defun some-func ()
#'(lambda (x) x))
;; At REPL
;; Does not work
> ((some-func) 1)
;; Does work
> ((lambda (x) x) 1)
;; Also works
> (funcall (some-func) 1)
Надеюсь это имеет смысл!
4 ответа
Это хороший вопрос, и тот, в котором Common Lisp может быть довольно запутанным. Дело в том, что по историческим причинам Common Lisp имеет два пространства имен - одно для функций, а другое для значений. Чтобы это произошло, есть два разных правила оценки для положения головы приложения-функции и для остальных - первое оценивает символ как имя функции, а второе оценивает символ как ссылку на переменную. Очевидно, есть случаи, когда значение на самом деле является функцией - например, если вы пишете mapcar
функция, вы хотите сделать что-то вроде
(defun my-mapcar (f l)
(if (null l)
'()
(cons (f (car l)) (my-mapcar f (cdr l)))))
но это не сработает - оно будет жаловаться f
будучи неизвестной функцией. Для этих случаев есть специальная функция под названием funcall
, который получает функцию и аргумент для функции, и будет применять функцию как обычно - и так как funcall
является простой функцией, все ее аргументы оцениваются как обычно (в виде значений). Таким образом, вышеприведенное должно быть исправлено с помощью этого:
(defun my-mapcar (f l)
(if (null l)
'()
(cons (funcall f (car l)) (my-mapcar f (cdr l)))))
Как вы, вероятно, подозреваете сейчас, есть зеркальные случаи - когда вы хотите оценить что-то как функцию, а не как значение. Например, это не работает:
(my-mapcar 1+ '(1 2 3))
потому что это относится к 1+
переменная, а не функция. Для этих случаев есть специальная форма function
который оценивает его содержимое как функцию и возвращает его как значение:
(my-mapcar (function 1+) '(1 2 3))
и это может быть сокращено с #'
:
(my-mapcar #'1+ '(1 2 3))
Это не конец этой истории - привести несколько примеров:
в некоторых случаях простое имя в кавычках может работать как функция - например,
'1+
в последнем примере работает - но это своего рода хак, который может видеть только глобально связанные имена, поэтому#'
почти всегда лучшеподобный взлом может быть использован с
lambda
- так что вы можете использовать(my-mapcar '(lambda (x) (1+ x)) '(1 2 3))
на самом деле, вы могли бы использовать(list 'lambda '(x) '(1+ x))
что еще хуже (и IIRC, непереносимый), но с использованием(lambda (x) (1+ x))
работает, так как он неявно завернут в#'
(попробуйте расширитьlambda
Форма как макрос, и вы увидите это). Связанный хак позволяет использоватьlambda
выражение в качестве главы приложения функции (что вы и пытались).let
etc связывают локальные значения, и в некоторых случаях вы захотите вместо этого связывать локальные функции - для этого есть новые конструкции привязки:flet
а такжеlabels
Если все это выглядит странно и / или слишком сложно, то вы не одиноки. Это одно из основных различий между Common Lisp и Scheme. (Затем различие приводит к изменениям в общих идиомах в обоих языках: код схемы имеет тенденцию использовать функции более высокого порядка гораздо чаще, чем код Common Lisp. Как обычно, с такими религиозными вопросами, некоторые люди спорят в пользу того, что делает CL, утверждая, что что функции высшего порядка сбивают с толку, поэтому им нравится явное напоминание в коде.)
((lambda (x) ...) ...)
это жестко запрограммированный особый случай в правилах оценки. Это не оценка первого элемента в форме, а использование результата в качестве общего случая. Это нормально, что вы должны использовать funcall
или же apply
вызвать функцию, которая является результатом оценки какой-либо другой формы.
Причина, по которой Common Lisp не позволяет функции, которая возвращает лямбду, быть первым элементом в выражении, связана с различием между Lisp-1 и Lisp-2.
Если разрешен Common Lisp ((some-func) 1)
быть эквивалентным (funcall (some-func) 1)
то это может быть воспринято как непоследовательное (let ((f #'some-func)) (f 1))
а не требовать (funcall f 1)
,
На самом деле есть очень хорошее оправдание, чтобы не поддерживать такие формы: они неоднозначны. Рассмотрим способ определения имен функций в Common Lisp:
имя функции n. 1. (в среде) Символ или список
(setf symbol)
это имя функции в этой среде. 2. Символ или список(setf symbol)
,
Учитывая это, как должна вести себя форма, подобная следующей?
((setf car) 10 x)
Если это установить x
автомобиль к стоимости 10
или он должен выполнить форму (setf car)
(что было бы ошибкой) и попытайтесь использовать его возвращаемое значение как функцию для вызова с аргументами 10
а также x
? Common Lisp не определяет ни поведение, и мне совсем не ясно, что это плохой выбор. В конце концов, насколько я могу видеть, ничто в стандарте не мешает соответствующим реализациям расширять определение допустимых форм для поддержки более широкого диапазона имен операторов (поэтому специальные функции setf-case здесь тоже не помогут).