Подтвердить что ты не робот

Почему # 'используется до лямбда в Common Lisp?

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

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

вместо

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

(lambda (x)... сама по себе является просто некоторой структурой списка без кавычек. Это ее появление в качестве аргумента специальной формы FUNCTION (function (lambda (x)... которая вызывает существование объекта функции)

а также:

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

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

Ответ 2

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

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

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

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

Теперь попробуйте приведенную версию

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

Если вы используете insist on using # ', вам нужно написать

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

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

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

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

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

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

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

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

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

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

Теперь вернемся к примеру с начала сообщения. Почему

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

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

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

не является функцией, но

(lambda (x) (+ x 10))

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

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

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

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

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

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

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

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

+ -> "I am the plus function"

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

(+ 1 2) -> 3

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

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

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

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

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

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

(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, который сообщает Lisp использовать значение в ячейке значения символа в качестве функционального определения. Вы делаете это с помощью функции 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-выражение оценивается в соответствии с изложенными выше правилами. Во-первых, Lisp выводит функциональное определение "mapcar". Затем Lisp извлекает содержимое своего второго аргумента (например, квадратного). Наконец, он возвращает '(1 2 3), не оцененный для третьего аргумента.

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

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

Вышеприведенный код будет оцениваться как

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

Цитата требуется в обоих

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

и

(symbol-value 'ASymbol)  

и

(symbol-function 'ASymbol)  

потому что иначе Lisp получит значение ASymbol в каждом случае, предотвращая корректную работу функции fset, symbol-value и функции-символа.

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