19

问题不在于使用关键字,而实际上是关于关键字实现。例如,当我使用关键字参数创建一些函数并进行调用时:

(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 中关键字的手册并解释了它们的机制,如果您在此处发布链接,我将不胜感激,因为我只能找到一些仅关于关键字用法的简短文章。

4

3 回答 3

11

有关关键字参数的完整详细信息,请参阅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,但这不是必需的;如果只指定 avar而不是(keyword-name var),则默认keyword-name为关键字包中与 . 同名的符号var

因此,例如,您可以这样做:

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

然后将其称为:

(fun 'myoption 3)

它在内部工作的方式是当函数被调用时,它会逐步遍历参数列表,收集成对的参数<label, value>。对于每一个label,它会在参数列表中查找具有该参数的参数keyword-name,并将对应的绑定varvalue

我们通常使用关键字的原因是因为:前缀突出。并且这些变量已经进行了自我评估,因此我们不必也引用它们,即你可以写:key-param而不是':key-param(仅供参考,后一种表示法在早期的 Lisp 系统中是必要的,但 CL 设计者认为它很丑,而且多余的)。而且我们通常不会使用指定与变量名称不同的关键字的功能,因为这会造成混淆。这样做是为了完全通用。此外,允许常规符号代替关键字对于 CLOS 等工具很有用,其中参数列表被合并并且您希望避免冲突 - 如果您正在扩展通用函数,您可以添加关键字在您自己的包中的参数不会发生碰撞。

在定义包和导出变量时使用关键字参数再次只是一种约定。 DEFPACKAGE, IN-PACKAGE,EXPORT等只关心他们的名字,而不关心它在什么包里。你可以写

(defpackage key-param)

它通常也可以正常工作。许多程序员不这样做的原因是因为它在他们自己的包中实习了一个符号,如果这个符号恰好与他们试图从另一个包中导入的符号同名,这有时会导致包冲突。使用关键字将这些参数从应用程序的包中分离出来,避免了这样的潜在问题。

底线是:当你使用一个符号并且你只关心它的名字而不是它的身份时,使用关键字通常是最安全的。

最后,关于以不同方式使用关键字时的区别。关键字只是一个符号。如果在函数或宏只需要普通参数的地方使用它,则该参数的值将是符号。如果您正在调用具有&key参数的函数,那是唯一一次将它们用作将参数与参数相关联的标签。

于 2012-12-25T08:01:45.647 回答
4

好的手册是PCL 的第 21 章

简要回答您的问题:

  • 关键字是包中的导出符号keyword,因此您不仅可以将它们称为:a,还可以称为keyword:a

  • 函数参数列表(称为lambda-lists)中的关键字可能以下列方式实现。在存在&key修饰符的情况下,lambda表单将扩展为类似于以下内容:

    (let ((key-param (getf args :key-param)))
      body)
    
  • 当您使用关键字命名包时,它实际上用作string-designator. 这是一个 Lisp 概念,它允许传递给某个处理字符串的函数,这些字符串稍后将用作符号(对于不同的名称:包、类、函数等),不仅是字符串,还有关键字和符号. 所以,定义/使用包的基本方法实际上是这样的:

    (defpackage "KEY-PARAM" ...)
    

    但你也可以使用:

    (defpackage :key-param ...)
    

    (defpackage #:key-param ...)
    

    (这里#:是一个读取器宏来创建不需要的符号;这种方式是首选方式,因为您不会在此过程中创建不需要的关键字)。

    后两种形式将转换为大写字符串。因此,关键字仍然是关键字,并且包将其命名为字符串,从该关键字转换而来。

总而言之,关键字具有自身的价值,以及任何其他符号。不同之处在于关键字不需要对keyword包或其显式使用进行显式限定。作为其他符号,它们可以作为对象的名称。例如,您可以使用关键字命名一个函数,它可以在每个包中“神奇地”访问 :) 有关详细信息,请参阅 @Xach 的博

于 2012-12-25T08:05:19.830 回答
1

不需要“系统”来区分关键字的不同用途。它们只是用作名称。例如,想象两个 plist:

(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

现在,系统如何区分关键字 in*language-scores*和 in *price*?绝对没有。关键字只是名称,在不同的数据结构中指定不同的事物。它们并不比自然语言中的同音异义词更显着——它们的使用决定了它们在给定上下文中的含义。

在上面的例子中,没有什么能阻止我们在错误的上下文中使用函数:

(language-score :basic *prices*)
100

语言并没有阻止我们这样做,不那么花哨的编程语言和不那么花哨的产品的关键字是一样的。

有很多可能性可以防止这种情况发生:首先不允许可选参数 for language-score,放入*prices*另一个包而不将其外部化,关闭词法绑定而不是使用全局特殊*language-scores*,而只公开添加和检索条目的方法。也许仅仅我们对代码库或约定的理解就足以阻止我们这样做。重点是:系统区分关键字本身并不是实现我们想要实现的功能所必需的。

您询问的特定关键字使用没有什么不同:实现可能将关键字参数的绑定存储在 alist、plist、哈希表或其他任何东西中。在包名称的情况下,关键字仅用作包指示符,而包名称作为字符串(大写)可能仅用作代替。实现是否将字符串转换为关键字、关键字转换为字符串或内部完全不同的东西并不重要。重要的只是名称,以及在什么上下文中使用它。

于 2012-12-25T08:03:57.513 回答