【问题标题】:How do I make named arguments in ELisp?如何在 ELisp 中创建命名参数?
【发布时间】:2014-09-29 14:58:55
【问题描述】:

我认为,由于 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 的习惯不够了解,不知道这是什么意思,而且没有多少 谷歌搜索 让我更接近答案。

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

【问题讨论】:

    标签: emacs elisp


    【解决方案1】:

    由于 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
    

    【讨论】:

      【解决方案2】:

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

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

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

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

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


      示例中的函数还包含对函数values 的调用。此函数特定于 Common Lisp,但在 cl-lib 中以 cl-values 的形式提供,在 cl 中以 values 的形式提供。

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

      在这样的 Emacs Lisp 函数中,您要么明确指定返回值为 nil,要么干脆将其省略,将最后一个形式的返回值作为函数的返回值,因为调用者预计不会使用返回值。

      【讨论】:

      • 这并不完全有效,我收到一条错误消息Symbol's function definition is void: values
      • 对,values 函数是 Common Lisp 的另一个特色。在cl-lib 中称为cl-values
      • 这样做只是让它返回 nil 而不是打印任何东西......至少它不会产生错误
      【解决方案3】:

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

      在核心,这意味着您将 map-like 数据结构传递给函数,因此您需要同时处理包装(将参数组合到 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)))))
      

      【讨论】:

      • 习惯上,您宁愿使用&rest args 并将args 解析为plist,这样语法混乱更少:(print-name :first "John" :last "Doe")
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多