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

Ele*_*fee 11 emacs elisp

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

有问题的代码如下所示:

(defun print-name (&key first (last "?"))
  (princ last)
  (when first
    (princ ", ")
    (princ first))
  (values))
Run Code Online (Sandbox Code Playgroud)

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

> (print-name)
  ?
> (print-name :first "John")
  ?, John
> (print-name :last "Doe")
  Doe
> (print-name :first "John" :last "Doe")
  Doe, John
Run Code Online (Sandbox Code Playgroud)

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

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

我在lisp中没有足够的常规来知道这应该是什么意思,并且没有任何谷歌搜索让我更接近答案.

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

lun*_*orn 17

由于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))))
Run Code Online (Sandbox Code Playgroud)

&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
Run Code Online (Sandbox Code Playgroud)


leg*_*cia 15

elisp的的defun不支持&key(它不支持&optional&rest,虽然).有一个宏可以让你使用定义函数&key.在Emacs 24.3及更高版本中,您可以要求cl-lib并使用cl-defun:

(require 'cl-lib)
(cl-defun print-name (&key first (last "?"))
   ...
Run Code Online (Sandbox Code Playgroud)

在早期的Emacs版本中,相同的功能在cl库中的名称下defun*:

(require 'cl)
(defun* print-name (&key first (last "?"))
   ...
Run Code Online (Sandbox Code Playgroud)

cl-lib库是首选cl,因为它通过在所有符号前加上来保持名称空间整洁cl-; 但是,如果您需要与早期的Emacs版本兼容,您可能更喜欢cldefun*.


示例中的函数还包含对函数的调用values.此函数特定于Common Lisp,但可以在cl-valuesin cl-libvaluesin中使用cl.

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

在这样的Emacs Lisp函数中,您要么显式指定返回值nil,要么完全不将它保留,将最后一个表单的返回值保留为函数的返回值,因为调用者不应该使用回报价值.


ele*_*kil 4

不幸的elisp 支持命名参数本身。但是,您可以通过将 an 传递alist给函数来模拟该功能,请检查此以获取有关 的指南alists

从本质上讲,这意味着您将类似映射的数据结构传递到函数中,因此您需要自己处理包装(将参数组合到 )alist和解包(分解为变量/检查必需/可选值) 。

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

(print-name '((first . "John") (last . "Doe")))
Run Code Online (Sandbox Code Playgroud)

读取就更简单了:使用即可assoc获取对应的值:

(assoc 'last '((first . "John") (last . "Doe")))
; => (last . "Doe")
(assoc 'middle '((first . "John") (last . "Doe")))
; => nil
Run Code Online (Sandbox Code Playgroud)

因此,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)))))
Run Code Online (Sandbox Code Playgroud)

  • 通常,您宁愿使用 `&rest args` 并将 `args` 解析为 plist,这样语法混乱较少:`(print-name :first "John" :last "Doe")`。 (8认同)