问题不在于使用关键字,而在于关键字实施。例如,当我使用关键字参数创建一些函数并进行调用时:
(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中有一些关于关键词的手册及其机制的解释,我将不胜感激,如果你在这里发布一个链接,因为我只能找到一些关于关键词用法的简短文章。
答案 0 :(得分: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
(仅供参考,后一种符号在早期的Lisp系统中是必需的但CL设计师认为它很丑陋且多余。我们通常不会使用能力来指定与变量具有不同名称的关键字,因为这会让人感到困惑。这样做是为了完全普遍。另外,允许使用常规符号代替关键字对于像CLOS这样的工具很有用,其中参数列表被合并并且你想避免冲突 - 如果你扩展了泛型函数,你可以添加关键字在你自己的包中的参数不会发生碰撞。
在定义包和导出变量时使用关键字参数只是一种约定。 DEFPACKAGE
,IN-PACKAGE
,EXPORT
等只关心他们给出的名字,而不是它所包含的包。你可以写
(defpackage key-param)
它通常也能正常工作。许多程序员不这样做的原因是因为它在自己的包中实现了一个符号,如果碰巧与他们试图从另一个包导入的符号具有相同的名称,这有时会导致包冲突。使用关键字从应用程序包中分离这些参数,避免这样的潜在问题。
底线是:当您使用符号而您只关心其名称而非其身份时,使用关键字通常最安全。
最后,关于在以不同方式使用关键字时的区分。关键字只是一个符号。如果它在函数或宏只需要普通参数的地方使用,那么该参数的值将是符号。如果您正在调用具有&key
个参数的函数,那么这是唯一一次将它们用作标签以将参数与参数相关联。
答案 1 :(得分:4)
好的手册是Chapter 21 of 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了解详细信息。
答案 2 :(得分:1)
“系统”无需区分关键字的不同用途。它们只是用作名称。例如,想象两个plists:
(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*
放在另一个包中而不对其进行外设,关闭词法绑定而不是使用全局特殊*language-scores*
,而只是暴露意味着添加和检索条目。也许只是我们对代码库或约定的理解足以阻止我们这样做。重点是:系统区分关键字本身并不是实现我们想要实现的目标所必需的。
您询问的特定关键字使用方法并不相同:实现可能会将关键字参数的绑定存储在alist,plist,hash-table或其他任何内容中。对于包名称,关键字仅用作包指示符,而包名称可以仅用作字符串(大写)。实现是将字符串转换为关键字,关键字转换为字符串,还是内部完全不同的东西并不重要。重要的只是名称,以及使用它的上下文。