Почему # используется до лямбды в Common Lisp? - PullRequest
34 голосов
/ 24 июля 2010

Я хотел бы знать, почему большинство кода Common Lisp, который я вижу, имеет такие вещи, как

(mapcar #'(lambda (x) (* x x)) '(1 2 3))

вместо

(mapcar (lambda (x) (* x x)) '(1 2 3))

, который, кажется, тоже работает. Я начинаю изучать Common Lisp, и, имея некоторый опыт в Scheme, это меня заинтриговало.

Редактировать: Я знаю, что вам нужно # 'с именами функций, потому что они находятся в другом пространстве имен, чем переменные. Мой вопрос как раз о # 'до лямбды, так как лямбда уже возвращает функциональный объект (я думаю). Тот факт, что из-за расширения макросов работает меньше лямбд, делает его более интригующим ...

Ответы [ 2 ]

37 голосов
/ 24 июля 2010

#'foo является сокращением для (function foo) читателем.

В CL есть несколько различных пространств имен, #'foo или (function foo) вернет функциональное значение из foo.

Возможно, вы захотите поискать "Lisp-1 против Lisp-2" , проверить другие вопросы Stackoverflow или прочитать старую статью Питмана и Габриэля , чтобы узнать больше о концепции множественных пространств имен (также называемых слотами или ячейками символов).

Причина, по которой в случае лямбды #' может быть опущена в CL, заключается в том, что это макрос, который расширяется таким образом (взято из Hyperspec ):

(lambda lambda-list [[declaration* | documentation]] form*)
==  (function (lambda lambda-list [[declaration* | documentation]] form*))
==  #'(lambda lambda-list [[declaration* | documentation]] form*)

#' все еще может использоваться по историческим причинам (я думаю, что в Maclisp lambda s не расширяется до формы функции), или потому, что некоторые люди думают, что пометка лямбда резкими кавычками может сделать код более читабельным или связный. В некоторых случаях это может иметь значение, но в целом не имеет значения, какую форму вы выберете.

Полагаю, вы можете думать об этом так: (function (lambda ...)) возвращает функцию, создаваемую (lambda ...). Обратите внимание, что lambda в CL Hyperspec имеет как макрос, так и символьную запись . Из последнего:

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

Из документации из function:

Если имя является лямбда-выражением, тогда Возвращается лексическое закрытие.

Я думаю, что разница также связана с вызовом лямбда-форм, подобных этой: ((lambda ...) ...), где она рассматривается как форма, подлежащая оценке, против (funcall #'(lambda ...) ...). Если вы хотите узнать больше по этой теме, есть ветка c.l.l .

Некоторые цитаты из этой темы:

(lambda (x) ... само по себе это всего лишь некоторые структура списка без кавычек. Это его появление в качестве аргумента ФУНКЦИЯ специальной формы (function (lambda (x) ..., которая вызывает существующий функциональный объект

и

Это также усугубляется тем, что макрос LAMBDA был довольно поздно Помимо ANSI Common Lisp, так что все действительно старых парней (то есть, как я) узнал их, когда тебе нужно было поставьте # для лямбда-выражения в функциях отображения. Иначе несуществующая лямбда-функция будет быть призванным.

Добавление макроса изменило это, но некоторые из нас слишком настроены на хочу поменять.

8 голосов
/ 06 июля 2017

Лучше всего избегать # в большинстве случаев, потому что это «в основном» не нужно и делает ваш код более многословным.Есть несколько исключений, когда необходима некоторая форма цитирования (см. Пример 4 ниже).

Примечание: Все примеры в этом посте были протестированы в Emacs Lisp (GNU Emacs 25.2.1), но они должны работать одинаково для любого общего ANISP-интерфейса.Основные понятия одинаковы на обоих диалектах.

ПРОСТОЕ ОБЪЯСНЕНИЕ
Сначала давайте рассмотрим случай, когда лучше всего избегать цитирования.Функции являются объектами первого класса (например, обрабатываются как любой другой объект, включая возможность передавать их функциям и назначать их переменным), которые оценивают сами себя.Анонимные функции (например, лямбда-формы) являются одним из таких примеров.Попробуйте следующее на Emacs Lisp (Mx ielm RET) или на ЛЮБОМ общем ANSI ANSI.

((lambda (x) (+ x 10)) 20) -> 30

Теперь попробуйте указанную версию

(#'(lambda (x) (+ x 10)) 20) -> "function error" or "invalid function..."  

Если вы используете настаивать на использовании # ', вы должны написать

(funcall #'(lambda (x) (+ x 10)) 20) -> 30

ПОДРОБНОЕОБЪЯСНЕНИЕ
Чтобы по-настоящему понять, когда требуется цитирование, нужно знать, как Лисп оценивает выражения.Читать дальше.Я обещаю сделать это кратким.

Вам нужно знать несколько основных фактов о Лиспе:

  1. Лисп "всегда" оценивает каждое выражение.Хорошо, если выражение не заключено в кавычки, в этом случае оно возвращается без оценки.
  2. Атомы оценивают сами себя.Атомарные выражения НЕ являются списками.Примеры включают числа, строки, хеш-таблицы и векторы.
  3. Символы (имена переменных) хранят два типа значений.Они могут содержать регулярные значения и функциональные определения.Следовательно, символы Lisp имеют два слота, называемых ячейками для хранения этих двух типов.Нефункциональный контент обычно хранится в ячейке значения символа и функционирует в функциональной ячейке.Возможность одновременно хранить как нефункциональные, так и функциональные определения помещает Emacs Lisp и Common Lisp в категорию 2-Lisp.Какая из двух ячеек используется в выражении, зависит от того, как используется символ, а точнее от его положения в списке.В отличие от символов в некоторых диалектах Лисп, Схема является наиболее известным, может содержать только одно значение.Схема не имеет понятия ячейки значения и функции.Такие Лиспы в совокупности называются 1-Лиспс.

Теперь вам необходимо приблизительное понимание того, как Лисп оценивает S-выражения (выражения в скобках).Каждое S-выражение оценивается примерно следующим образом:

  1. Если указано, вернуть его без оценки
  2. Если не указано в кавычках, получить его CAR (например, первый элемент) и оценить его, используяследующие правила:

    a.если атом, просто вернуть его значение (например, 3 -> 3, "pablo" -> "pablo")
    b.если S-выражение, оцените его, используя ту же самую общую процедуру
    c.если символ, вернуть содержимое его функциональной ячейки

  3. Оценить каждый из элементов в CDR S-выражения (например, все, кроме первого элемента списка).

  4. Примените функцию, полученную из CAR, к значениям, полученным от каждого из элементов в CDR.

Вышеуказанная процедура подразумевает, что любой символ в CAR из UNQUOTED S-выражение должно иметь действительное функциональное определение в своей функциональной ячейке.

Теперь давайте вернемся к примеру с начала поста.Почему

(#'(lambda (x) (+ x 10)) 20)  

генерирует ошибку?Это происходит потому, что # '(lambda (x) (+ x 10)), CAR S-выражения, не оценивается интерпретатором Lisp из-за функциональной кавычки #'.

#'(lambda (x) (+ x 10))

isне функция, но

(lambda (x) (+ x 10))

есть.Имейте в виду, что целью цитирования является предотвращение оценки.С другой стороны, лямбда-форма оценивает себя, функциональную форму, которая действительна как CAR списка UNQUOTED .Когда Lisp оценивает CAR

((lambda (x) (+ x 10)) 20)  

оно получает (лямбда (х) (+ х 20)), что является функцией, которая может применяться к остальным аргументам в списке (при условии, что длина CDR равна числу аргументов, разрешенных лямбда-выражением)выражение).Следовательно,

((lambda (x) (+ x 10)) 20) -> 30  

Таким образом, возникает вопрос, когда заключать в кавычки функции или символы, содержащие функциональные определения.Ответ почти НИКОГДА, если вы не делаете вещи "неправильно".Под «неправильно» я подразумеваю, что вы помещаете функциональное определение в ячейку значения символа или функциональную ячейку, когда вы должны сделать обратное.Для лучшего понимания см. Следующие примеры:

ПРИМЕР 1 - Функции, хранящиеся в ячейках значений
Предположим, вам нужно использовать «apply» с функцией, которая ожидает переменное числоаргументы.Одним из таких примеров является символ +.Лисп рассматривает + как обычный символ.Функциональное определение хранится в функциональной ячейке +.Вы можете присвоить значение его ячейке значения, если хотите, используя

(setq + "I am the plus function").  

Если вы оцениваете

+ -> "I am the plus function"

Однако (+ 1 2) по-прежнему работает, как и ожидалось.

(+ 1 2) -> 3

Функция apply весьма полезна в рекурсии.Предположим, вы хотите суммировать все элементы в списке.Вы НЕ МОЖЕТЕ писать

(+ '(1 2 3)) -> Wrong type...  

Причина в том, что + ожидает, что его аргументы будут функциями.apply решает эту проблему

(apply #'+ '(1 2 3)) -> (+ 1 2 3) -> 6  

Почему я цитировал + выше?Помните правила оценки, которые я изложил выше.Лисп оценивает применяемый символ путем извлечения значения, хранящегося в его функциональной ячейке.он получает функциональную процедуру, которую может применить к списку аргументов.Однако, если я не буду заключать в кавычки +, Lisp будет извлекать значение, хранящееся в его ячейке значения, потому что это НЕ первый элемент в S-выражении.Поскольку для ячейки значения + установлено значение «Я - функция плюс», Лисп не получает функционального определения, содержащегося в ячейке функции +.На самом деле, если бы мы не установили для его ячейки значения значение «Я являюсь функцией плюс», Лисп получил бы значение nil, которое НЕ является функцией, как того требует применение.

Есть ли способ использовать + без кавычек?с применением.Да, есть.Вы можете просто оценить следующий код:

(setq + (symbol-function '+))  
(apply + '(1 2 3))  

Это даст оценку 6, как и ожидалось, потому что, как оценивает Lisp (apply + '(1 2 3)), теперь он находит функциональное определение +, хранящееся в +Ячейка значения.

ПРИМЕР 2 - Сохранение функциональных определений в ячейках значений
Предположим, вы сохраняете функциональное определение в ячейке значений символа.Это достигается следующим образом:

(setq AFunc (lambda (x) (* 10 x)))

Оценка

(AFunc 2)

генерирует ошибку, потому что Lisp не может найти функцию в функциональной ячейке AFunc.Вы можете обойти это, используя funcall, который говорит Лиспу использовать значение в ячейке значения символа в качестве функционального определения.Вы делаете это с помощью «funcall».

(funcall AFunc 2)

Если допустимо функциональное определение, хранящееся в ячейке значения символа,

(funcall AFunc 2) -> 20  

Вы можете избежать использования funcall, поместив лямбда-формув функциональной ячейке символа с помощью fset:

(setf AFunc (lambda (x) (* 10 x)))  
(AFunc 2)  

Этот кодовый блок вернет 20, потому что lisp находит функциональное определение в функциональной ячейке AFunc.

ПРИМЕР 3 - Локальные функции
Скажем, вы пишете функцию и вам нужна функция, которая больше нигде не будет использоваться.Типичным решением является определение функции, действительной только внутри основной области.Попробуйте это:

(defun SquareNumberList (AListOfIntegers)
    "A silly function with an uncessary
   local function."
  (let ((Square (lambda (ANumber) (* ANumber ANumber))))
    (mapcar Square AListOfIntegers)
    )
  )  

(SquareNumberList '(1 2 3))  

Этот блок кода вернет

(1 4 9)  

Причина, по которой в приведенном выше примере Square не указана, заключается в том, что S-выражение вычисляется в соответствии с правилами, изложенными выше.,Сначала Лисп использует функциональное определение mapcar.Затем Lisp извлекает содержимое ячейки значения своего второго аргумента (например, 'Square).Наконец, он возвращает '(1 2 3) без оценки для третьего аргумента.

ПРИМЕР 4 - Содержание значения символа и функциональных ячеек
Вот один случай, когда требуются кавычки.

(setq ASymbol "Symbol's Value")  
(fset 'ASymbol (lambda () "Symbol's Function"))  
(progn  
  (print (format "Symbol's value -> %s" (symbol-value 'ASymbol)))  
  (print (format "Symbol's function -> %s" (symbol-function 'ASymbol)))
  )    

Приведенный выше код будет иметь значение

"Symbol's value -> Symbol's Value"  
"Symbol's function -> (lambda nil Symbol's Function)"  
nil

Кавычки требуются как в

(fset 'ASymbol (lambda () "Symbol's Function"))  

, так и в

(symbol-value 'ASymbol)  

и

(symbol-function 'ASymbol)  

, поскольку в противном случае Lisp получит значение ASymbol в каждом случаепредотвращение правильной работы fset, symbol-value и symbol-function.

Надеюсь, этот длинный пост окажется полезным для кого-то.

...