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

18

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

(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 есть какое-то руководство по ключевым словам, с объяснением их механики, я был бы признателен, если бы вы разместили ссылку здесь, потому что я мог найти только некоторые короткие статьи только об использовании ключевых слов.

    
задан TheEnt 25.12.2012 в 07:17
источник

3 ответа

10

Подробнее о параметрах ключевых слов см. Common 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 , это единственный раз, когда они используются в качестве меток для сопоставления аргументов с параметрами.

    
ответ дан Barmar 25.12.2012 в 09:01
  • Спасибо! Теперь все ясно, это действительно полезно! –  TheEnt 25.12.2012 в 09:29
  • @Barmar "(defun fun (& key key-param) ...) на самом деле не хватает для: (defun fun (& key ((: key-param key-param))) ...)", за исключением того, что после первого символа символ «KEY-PARAM» еще не был интернирован в пакете ключевых слов (по крайней мере, согласно расшифровке OP). Если бы это была сокращенная версия, в которой расширялся arglist (и, возможно, реализация могла бы это сделать, я не уверен, разрешено ли это или нет), тогда ключевое слово было бы интернировано в момент макрорасширения. (Это не особенно важно, но это указывает на «утечку» (или нет) некоторую реализацию defun). –  Joshua Taylor 11.08.2017 в 22:36
4

Хорошее руководство Глава 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 или его явным использованием. И как другие символы, они могут служить именами для объектов. Например, вы можете назвать функцию с ключевым словом, и она будет «волшебным образом» доступна в каждом пакете :) См. @aach blogpost для получения более подробной информации.

    
ответ дан Vsevolod Dyomkin 25.12.2012 в 09:05
  • Спасибо! (getf: key-param args) - это действительно хорошее объяснение. Кроме того, я пробовал функции с ключевыми словами, в Lispworks что-то вроде (defun: test () «hi») вызывает ошибку. Определяющая функция: TEST видна из пакета KEYWORD. (но есть опция отладчика «Определить это в любом случае»), в то время как в SBCL все работает. –  TheEnt 25.12.2012 в 09:36
  • Добро пожаловать! Я также должен сказать, что я случайно написал это в неправильном порядке - правильный способ (getf args: key-param) :) –  Vsevolod Dyomkin 25.12.2012 в 10:16
1

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

(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, хеш-таблице или что-то еще. В случае имен пакетов ключевые слова просто используются в качестве обозначений пакетов, а имя пакета в виде строки (в верхнем регистре) может быть просто использовано. Является ли реализация конвертирует строки в ключевые слова, ключевые слова в строки или что-то совершенно другое внутренне, не имеет большого значения. Важно только имя и в каком контексте оно используется.

    
ответ дан danlei 25.12.2012 в 09:03
  • Спасибо! Еще один вопрос: можно ли проверять, преобразует ли ключевые слова в строки или нет? Можно ли сменить ключевые слова на строки? Я имею в виду, что когда читатель Lisp находит ключевое слово, он анализирует его как особый тип строки. –  TheEnt 25.12.2012 в 09:36
  • Чтобы проверить, как выполняется внутренняя реализация, вам нужно взглянуть на исходный код (или подробную документацию по конкретной конкретной конкретной реализации). В случае имен пакетов я бы предположил реализации для сравнения строк, потому что в противном случае (с настройками чтения по умолчанию) информация о случаях будет потеряна. Хотя два пакета «foo» и «Foo» отличаются друг от друга, два ключевых слова: foo и: Foo - нет. Они оба интернированы с тем же именем-символом, «FOO». (Но тогда есть также: | Foo |, чтобы этого не было.) –  danlei 25.12.2012 в 09:52