Использование лямбда-значения из функции в качестве первого элемента списка

Я читаю "Парадигмы программирования искусственного интеллекта" Питера Норвига и столкнулся с проблемой, которую не могу решить самостоятельно (это мое введение в Лисп). На самом деле проблема довольно маленькая, но, очевидно, не та, которую мой маленький мозг может решить.

Почему, когда значение функции является лямбда-выражением, ошибочно использовать эту функцию в качестве первого элемента списка. Например:

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 здесь тоже не помогут).

Другие вопросы по тегам