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

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

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

Лисп:

(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 ]

6 голосов
/ 01 мая 2011

Это хороший вопрос, и тот, в котором 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 и т.д. bindлокальные значения, и в некоторых случаях вам нужно вместо этого связывать локальные функции - для этого есть новые конструкции привязки: flet и labels

Если все этовыглядит странно и / или слишком сложно, тогда вы не одиноки.Это одно из главных отличий между Common Lisp и Scheme.(Затем различие приводит к изменениям в общих идиомах в обоих языках: код схемы имеет тенденцию использовать функции более высокого порядка гораздо чаще, чем код Common Lisp. Как обычно, с такими религиозными вопросами, некоторые люди спорят в пользу того, что делает CL, утверждая, чточто функции высшего порядка сбивают с толку, поэтому им нравится явное напоминание в коде.)

4 голосов
/ 01 мая 2011

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

2 голосов
/ 01 мая 2011

Причина, по которой Common Lisp не позволяет функции, которая возвращает лямбду, быть первым элементом в выражении, связана с различием между Lisp-1 и Lisp-2 .

Если Common Lisp допускает, что ((some-func) 1) эквивалентен (funcall (some-func) 1), то это может восприниматься как непоследовательное, что также не позволяет сказать (let ((f #'some-func)) (f 1)), а не требует (funcall f 1).

1 голос
/ 02 мая 2011

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

имя функции n . 1. (в среде ) символ или список (setf symbol), который является именем функции в этой среде . 2. Символ или список (setf symbol).

Учитывая это, как должна вести себя форма, подобная следующей?

((setf car) 10 x)

Должно ли это установить для машины x значение 10 или выполнить форму (setf car) (что было бы ошибкой) и попытаться использовать ее возвращаемое значение в качестве функции для вызова с аргументами 10 и x? Common Lisp не определяет ни поведение, и мне совершенно не ясно, что это плохой выбор. В конце концов, насколько я понимаю, ничто в стандарте не мешает соответствующим реализациям расширять определение допустимых форм для поддержки более широкого диапазона имен операторов (поэтому специальные функции setf-кожуха здесь тоже не помогут).

...