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

Как ключевые слова работают в Common Lisp?

Вопрос не в использовании ключевых слов, а в действительности о реализации ключевых слов. Например, когда я создаю некоторую функцию с параметрами ключевого слова и делаю вызов

(defun fun (&key key-param) (print key-param)) => FUN
(find-symbol "KEY-PARAM" 'keyword) => NIL, NIL   ;;keyword is not still registered
(fun :key-param 1) => 1
(find-symbol "KEY-PARAM" 'keyword) => :KEY-PARAM, :EXTERNAL

Как использовать ключевое слово для передачи аргумента? Ключевые слова являются символами, значения которых сами по себе, поэтому как связать соответствующий параметр с помощью ключевого слова?

Другой вопрос о ключевых словах - ключевые слова используются для определения пакетов. Мы можем определить пакет с уже существующим ключевым словом

(defpackage :KEY-PARAM) => #<The KEY-PARAMETER package, 0/16 ...
(in-package :KEY-PARAM) => #<The KEY-PARAMETER package, 0/16 ...
(defun fun (&key key-param) (print key-param)) => FUN
(fun :KEY-PARAM 1) => 1

Как система отличает использование :KEY-PARAM между именем пакета и именем параметра функции? Также мы можем сделать что-то более сложное, если мы определим функцию KEY-PARAM и экспортируем ее (фактически не функцию, а имя)

(in-package :KEY-PARAM)
(defun KEY-PARAM (&key KEY-PARAM) KEY-PARAM) => KEY-PARAM
(defpackage :KEY-PARAM (:export :KEY-PARAM))  
   ;;exporting function KEY-PARAM, :KEY-PARAM keyword is used for it
(in-package :CL-USER) => #<The COMMON-LISP-USER package, ...
(KEY-PARAM:KEY-PARAM :KEY-PARAM 1) => 1
   ;;calling a function KEY-PARAM from :KEY-PARAM package with :KEY-PARAM parameter...

Вопрос тот же, как Common Lisp отличает использование ключевого слова: KEY-PARAM здесь?

Если есть несколько руководств по ключевым словам в Common Lisp с объяснением их механики, я был бы признателен, если бы вы разместили ссылку здесь, потому что я мог найти только некоторые короткие статьи только об использовании ключевых слов.

4b9b3361

Ответ 1

Подробнее о параметрах ключевых слов см. Общие Lisp Hyperspec. Обратите внимание, что

(defun fun (&key key-param) ...)

на самом деле короткий для:

(defun fun (&key ((:key-param key-param)) ) ...)

Полный синтаксис параметра ключевого слова:

((keyword-name var) default-value supplied-p-var)

default-value и supplied-p-var являются необязательными. Хотя условно использовать символ ключевого слова как keyword-name, он не требуется; если вы просто укажете var вместо (keyword-name var), по умолчанию keyword-name будет символом в пакете ключевых слов с тем же именем, что и var.

Так, например, вы могли бы сделать:

(defun fun2 (&key ((myoption var))) (print var))

а затем назовите его как:

(fun 'myoption 3)

Способ, которым он работает внутри, - это когда функция вызывается, она проходит через список аргументов, собирая пары аргументов <label, value>. Для каждого label он просматривает список параметров для параметра с keyword-name и связывает соответствующий var с value.

Причина, по которой мы обычно используем ключевые слова, состоит в том, что префикс : выделяется. И эти переменные были сделаны самооценками, поэтому нам не нужно их процитировать, т.е. Вы можете написать :key-param вместо ':key-param (FYI, эта последняя запись была необходима в более ранних системах Lisp, но дизайнеры CL решили, что он уродливый и избыточный). И мы обычно не используем возможность указывать ключевое слово с другим именем из переменной, потому что это будет путать. Это было сделано для полной общности. Кроме того, использование регулярных символов вместо ключевых слов полезно для таких объектов, как CLOS, где списки аргументов объединяются, и вы хотите избежать конфликтов - если вы расширяете общую функцию, вы можете добавлять параметры, ключевые слова которых находятся в вашем собственном пакете, и там не будут столкновениями.

Использование аргументов ключевого слова при определении пакетов и экспортировании переменных снова является просто конвенцией. DEFPACKAGE, IN-PACKAGE, EXPORT и т.д. заботятся только об именах, которые им заданы, а не о том, в каком пакете они содержатся. Вы могли бы написать

(defpackage key-param)

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

Суть заключается в следующем: когда вы используете символ, и вы заботитесь только о его имени, а не о его личности, он часто безопаснее использовать ключевое слово.

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

Ответ 2

Хорошее руководство Глава 21 PCL.

Отвечая на ваши вопросы кратко:

  • ключевые слова экспортируются символы в keyword, поэтому вы можете ссылаться на них не только как :a, но также как keyword:a

  • ключевые слова в списках аргументов функций (называемые lambda-list s), вероятно, реализованы следующим образом. В присутствии модификатора &key форма lambda раскрывается во что-то похожее на это:

    (let ((key-param (getf args :key-param)))
      body)
    
  • когда вы используете ключевое слово для обозначения пакета, оно фактически используется как string-designator. Это концепция Lisp, которая позволяет перейти к определенной функции, связанной со строками, которые позже будут использоваться в качестве символов (для разных имен: пакетов, классов, функций и т.д.) Не только строки, но и ключевые слова и символы. Таким образом, основной способ определения/использования пакета на самом деле таков:

    (defpackage "KEY-PARAM" ...)
    

    Но вы также можете использовать:

    (defpackage :key-param ...)
    

    и

    (defpackage #:key-param ...)
    

    (здесь #: - это макрос читателя для создания неинтерминированных символов, и этот способ является предпочтительным, потому что вы не создаете ненужные ключевые слова в процессе).

    Последние две формы будут преобразованы в строки верхнего регистра. Таким образом, ключевое слово остается ключевым словом, а пакет получает его имя как строку, преобразованное из этого ключевого слова.

Подводя итоги, ключевые слова имеют значение самих себя, а также любые другие символы. Разница в том, что ключевые слова не требуют явной квалификации с пакетом keyword или его явным использованием. И как другие символы, они могут служить именами для объектов. Например, вы можете назвать функцию с ключевым словом, и она будет "волшебным образом" доступна в каждом пакете. См. @Xach blogpost для подробности.

Ответ 3

Нет необходимости в "системе" различать различные применения ключевых слов. Они просто используются как имена. Например, представьте себе два слоя:

(defparameter *language-scores* '(:basic 0 :common-lisp 5 :python 3))
(defparameter *price* '(:basic 100 :fancy 500))

Функция, дающая оценку языка:

(defun language-score (language &optional (language-scores *language-scores*))
  (getf language-scores language))

Ключевые слова при использовании с language-score обозначают разные языки программирования:

CL-USER> (language-score :common-lisp)
5

Теперь, что делает система, чтобы отличать ключевые слова в *language-scores* от тех, что были в *price*? Абсолютно ничего. Ключевые слова - это просто имена, обозначающие разные вещи в разных структурах данных. Они не более отличаются от гомофонов на естественном языке - их использование определяет, что они означают в данном контексте.

В приведенном выше примере ничего не мешает нам использовать функцию с неправильным контекстом:

(language-score :basic *prices*)
100

Язык не сделал ничего, что помешало нам сделать это, и ключевые слова для не очень привлекательного языка программирования и не очень привлекательного продукта одинаковы.

Есть много возможностей предотвратить это: не разрешать необязательный аргумент для language-score, в первую очередь, помещая *prices* в другой пакет без его externing, закрывая лексическую привязку вместо того, чтобы использовать глобально специальный *language-scores* в то время как только выставление означает добавление и извлечение записей. Может быть, просто наше понимание базы кода или конвенции достаточно, чтобы помешать нам это сделать. Дело в том, что система, отличающая ключевые слова, не нужна для достижения того, что мы хотим реализовать.

Конкретное использование ключевых слов, о которых вы спрашиваете, не отличается: реализация может хранить привязки аргументов ключевого слова в alist, plist, хеш-таблице или что-то еще. В случае имен пакетов ключевые слова просто используются в качестве обозначений пакетов, а имя пакета в виде строки (в верхнем регистре) может быть просто использовано. Является ли реализация конвертирует строки в ключевые слова, ключевые слова в строки или что-то совершенно другое внутренне, не имеет большого значения. Важно только имя и в каком контексте оно используется.