Emacs 如何在ELisp中生成命名参数?

Emacs 如何在ELisp中生成命名参数?,emacs,elisp,Emacs,Elisp,我想既然Emacs-Lisp和Common-Lisp在语法上似乎有着如此密切的关系,我可以按照我在上面找到的示例代码来做,但结果证明我错了 所讨论的代码如下所示: (defun print-name (&key first (last "?")) (princ last) (when first (princ ", ") (princ first)) (values)) 根据RosettaCode,它应该执行以下操作: > (print-name)

我想既然Emacs-Lisp和Common-Lisp在语法上似乎有着如此密切的关系,我可以按照我在上面找到的示例代码来做,但结果证明我错了

所讨论的代码如下所示:

(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
我没有足够的口齿不清的习惯来理解这意味着什么,再多的谷歌搜索也没有让我更接近一个答案


那么,在Emacs Lisp中执行此操作的正确方法是什么呢?

不幸的是
elisp
。但是,您可以通过向函数传递
alist
来模拟该功能

在核心部分,这意味着您将一个类似映射的数据结构传递到函数中,因此您需要自己处理包装(将参数组合到
列表中)和展开(分解为变量/检查必需/可选值)

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

(打印名称“((第一个“约翰”)(最后一个“Doe”))
读取更简单:只需使用
assoc
即可获得相应的值:

(关联“最后”(first.John)(last.Doe)))
; => (最后一个“Doe”)
(中间名)(第一个“约翰”)(最后一个“Doe”))
; => 无
因此,
print name
可以如下所示:

(defun print-name (&key first (last "?"))
  (princ last)
  (when first
    (princ ", ")
    (princ first))
  (values))
(定义打印名称(列表)
(让((第一名(协会第一名))
(最后一个(关联的最后一个列表)))
(如果最后一次
(普林斯(cdr最后一任))
(错误“缺少姓氏!”)
(第一次
(普林斯)
(普林斯(cdr第一(()())))

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
库中,名称为
defun*

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


示例中的函数还包含对函数
值的调用。此函数特定于通用Lisp,但在
cl-lib
中作为
cl-values
提供,在
cl
中作为
values
提供

但是,这些备选方案的工作原理与它们的公共Lisp对应项并不完全相同。Common Lisp具有多个返回值的概念。例如,调用
(值1 2 3)
将返回三个值,而如上调用
(值)
将返回零值。Emacs Lisp函数通过列表模拟多个返回值,并重新定义
多值绑定
以匹配。这意味着调用
(cl值)
将只返回
nil
(空列表)


在这样的Emacs Lisp函数中,您可以显式地将返回值指定为
nil
,或者干脆不使用它,将最后一个表单的返回值作为函数的返回值,因为调用方不希望使用返回值。

因为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

习惯用法上,您更愿意使用
&rest args
和parse
args
作为plist,它具有较少的语法混乱:
(打印名:first“John”:last“Doe”)
。这不完全有效,我收到一个错误,说
符号的函数定义无效:value
对,
values
函数是另一个常见的Lisp特性。它在
cl-lib
中被称为
cl-values
。。。至少它不会产生错误