如何在ELisp中创建命名参数?

时间:2014-09-29 14:58:55

标签: emacs elisp

我认为既然Emacs Lisp和Common Lisp在语法方面看起来非常相似,我可以按照我在RosettaCode找到的示例代码,但事实证明我错了。

有问题的代码如下:

(defun print-name (&key first (last "?"))
  (princ last)
  (when first
    (princ ", ")
    (princ first))
  (values))

根据RosettaCode,它应该执行以下操作:

> (print-name)
  ?
> (print-name :first "John")
  ?, John
> (print-name :last "Doe")
  Doe
> (print-name :first "John" :last "Doe")
  Doe, John

现在,这就是事情;每当我尝试在我的ELisp解释器中运行该函数时,我都会收到以下错误:

*** Eval error ***  Wrong number of arguments: (lambda (&key first (last "?")) (princ la\
st) (if first (progn (princ ", ") (princ first))) (values)), 0

我在lisp中没有足够的常规来知道这意味着什么,并且没有任何 Google搜索让我更接近答案。

那么在Emacs Lisp中这样做的正确方法是什么?

3 个答案:

答案 0 :(得分:16)

由于Emacs Lisp不直接支持关键字参数,因此您需要使用cl-defun模拟这些参数,或者通过将参数解析为plist:

(defun print-name (&rest args)
  (let ((first (plist-get args :first))
        (last (or (plist-get args :last) "?")))
    (princ last)
    (when first
      (princ ", ")
      (princ first))))

&rest args告诉Emacs将所有函数参数放入单个列表中。 plist-get从属性列表中提取值,即格式(key1 value1 key2 value2 …)的列表。实际上,plist是一个扁平的alist。

这可以让您像问题一样致电print-name

> (print-name)
  ?
> (print-name :first "John")
  ?, John
> (print-name :last "Doe")
  Doe
> (print-name :first "John" :last "Doe")
  Doe, John

答案 1 :(得分:13)

Elisp的defun不支持&key(它确实支持&optional&rest。有一个宏可以让您使用&key定义函数。在Emacs 24.3及更高版本中,您可以要求cl-lib并使用cl-defun

(require 'cl-lib)
(cl-defun print-name (&key first (last "?"))
   ...

在早期的Emacs版本中,相同的功能位于名为cl的{​​{1}}库中:

defun*

(require 'cl) (defun* print-name (&key first (last "?")) ... 库优先于cl-lib,因为它通过在cl前添加所有符号来保持名称空间整洁;但是,如果您需要与早期的Emacs版本兼容,则可能更喜欢cl-cl


示例中的函数还包含对函数defun*的调用。此函数特定于Common Lisp,但values中的cl-valuescl-lib中的values可用。

然而,替代方案与Common Lisp对应方式完全不同。 Common Lisp具有多个返回值的概念。例如,调用cl将返回三个值 - 并且如上所述调用(values 1 2 3)将返回零值。 Emacs Lisp函数通过列表模拟多个返回值,并重新定义(values)以匹配。这意味着调用multiple-value-bind将只返回(cl-values)(空列表)。

在这样的Emacs Lisp函数中,您要么将返回值显式指定为nil,要么将其完全保留,将最后一个表单的返回值保留为函数的返回值,因为不希望调用者使用返回值。

答案 2 :(得分:3)

不幸的是elisp does not support named arguments per-se。但是,您可以通过将alist传递给函数check this for a guide on alists来模拟该功能。

核心,这意味着您将类似地图的数据结构传递到函数中,因此您需要处理两种包装(将参数组合到alist中)并自行解包(分解成变量/检查所需/可选值)。

创建输入结构很简单,只需构成变量符号的cons单元及其值:

(print-name '((first . "John") (last . "Doe")))

阅读更简单:只需使用assoc获取相应的值:

(assoc 'last '((first . "John") (last . "Doe")))
; => (last . "Doe")
(assoc 'middle '((first . "John") (last . "Doe")))
; => nil

因此,print-name可能如下所示:

(defun print-name (alist)
  (let ((first (assoc 'first alist))
        (last (assoc 'last alist)))
    (if last
        (princ (cdr last))
      (error "Missing last name!"))
    (when first
      (princ ", ")
      (princ (cdr first)))))