13
votes

I figured that since Emacs Lisp and Common Lisp seemed so closely related syntax wise, I could just follow the example code I found on RosettaCode, but it turns out that I was wrong.

The code in question looks like this:

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

And according to RosettaCode it should do the following:

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

Now, here's the thing; whenever I try to run that function in my ELisp interpreter, I get the following error:

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

I'm not routined enough in lisp to know what that's supposed to mean, and no amount of Googling has lead me any closer to an answer.

So what's the correct way of doing this in Emacs Lisp?

3

3 Answers

22
votes

Since Emacs Lisp does not support keyword arguments directly, you'll need to emulate these, either with cl-defun as in the other answer, or by parsing the arguments as 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 tells Emacs to put all function arguments into a single list. plist-get extracts a value from a property list, that is, a list of the format (key1 value1 key2 value2 …). Effectively, a plist is a flattened alist.

Together this lets you call print-name just like in your question:

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

Elisp's defun does not support &key (it does support &optional and &rest, though). There is a macro that lets you define functions using &key. In Emacs 24.3 and later you can require cl-lib and use cl-defun:

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

In earlier Emacs versions, the same functionality is in the cl library under the name defun*:

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

The cl-lib library is preferred to cl, since it keeps the namespace tidy by prefixing all symbols with cl-; however, if you need compatibility with earlier Emacs versions, you may prefer cl and defun*.


The function in the example also contains a call to the function values. This function is specific to Common Lisp, but is available as cl-values in cl-lib, and values in cl.

However, the alternatives don't work exactly the same as their Common Lisp counterpart. Common Lisp has the concept of multiple return values. For example, calling (values 1 2 3) would return three values — and calling (values) as above would return zero values. The Emacs Lisp functions emulate multiple return values by way of lists, and also redefines multiple-value-bind to match. This means that the call (cl-values) will just return nil (the empty list).

In such an Emacs Lisp function, you'd either explicitly specify the return value as nil, or just leave it out altogether, leaving the return value of the last form as the return value of the function, since the caller is not expected to use the return value.

4
votes

Unfortunately elisp does not support named arguments per-se. However, you can emulate that feature by passing an alist to a function, check this for a guide on alists.

At the core, this means that you pass a map-like data structure into the function, hence you need to take care of both wrapping (composition of arguments into the alist) and unwrapping (decomposition into variables / checking of required / optional values) yourself.

Creating of the input structure is simple, just compose cons cells of the variable symbol and its value:

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

Reading is even simpler: just use assoc to obtain the corresponding value:

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

Hence, print-name could look like this:

(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)))))