dash.el icon indicating copy to clipboard operation
dash.el copied to clipboard

[Feature request] defun with &rest and &key

Open ndwarshuis opened this issue 5 years ago • 5 comments

I am thinking of a macro to define a function like this:

(-defun-kw foo (a b &key one (two "2") &rest args)
  (list a b :one one :two two :rest args))

(foo 1 2)                      ; => (1 2 :one nil :two "2" :rest nil)
(foo 1 2 :two "1")             ; => (1 2 :one nil :two "1" :rest nil)
(foo 1 2 :two "1" 'r1 'r2 'r3) ; => (1 2 :one nil :two "1" :rest (r1 r2 r3))
(foo 1 2 'r1 'r2 'r3)          ; => (1 2 :one nil :two "2" :rest (r1 r2 r3))

This behavior is impossible with cl-defun (both the one in Emacs and the official CL version according to the CL-hyperspec). This is because using &rest with &key with cl-defun will simply bind the entire plist consisting of the keyval pairs to the symbol after &rest; it won't interpret "remaining arguments" as non-keyed arguments.

The only way I know to almost do this with core code in emacs is to bind a plist to one of the positional arguments and use &rest after.

I have code for this working in one of my repositories here. Its more complex than what I'm proposing here because it has a predicate checker like cl-defun (I'm not sure I'm keeping that though).

The way it works is that for each function call, anything that is not a positional argument is bound to an intermediate &rest-like symbol, and the list bound to this symbol is partitioned into a plist for the &key arguments and another normal list with whatever is leftover for &rest. The values for each key are then bound to symbols matching the keys without the colon, and the rest argument list is bound to the symbol after &rest in the signature like normal.

I don't think &optional is necessary since &key arguments can function like optional arguments, and adding &optional might not be worth the extra complexity.

ndwarshuis avatar Jan 10 '20 00:01 ndwarshuis

First of all, om.el seems like extremly rad package! I started something similar on my own (https://github.com/Fuco1/orgba), targeting more of org not just the elements, but I'm going to drop that part and use your package instead. Great!

As for a -defun, I think the simplest implementation would be

(defmacro -defun (name args &rest body)
  (declare (indent defun))
  `(defalias ',name
     (-lambda ,args ,@body)))


(-defun my-hello (one (&plist 'foo 'bar))
  (message "one %s foo %s bar %s"
           one
           foo
           bar))

(my-hello 1 '(foo "foo" bar "bar"))

However lists don't support &rest so we would need to implement that (there's only the . matcher). Still you would need to write:

(-defun my-hello (one (&plist 'foo 'bar) (&alist :whatever) (&rest args))
  )

A simple preprocessor can be created to wrap parts between keywords into lists, so one can write it without the extra parens as

(-defun my-hello (one &plist 'foo 'bar &alist :whatever &rest args)
  )
(defun preprocess-arguments (args)
  (magic args))

(defmacro -defun (name args &rest body)
  (declare (indent defun))
  `(defalias ',name
     (-lambda ,(preprocess-arguments args) ,@body)))

Fuco1 avatar Jan 10 '20 12:01 Fuco1

#268 is relevant as well, at least in name.

alphapapa avatar Jan 10 '20 13:01 alphapapa

@Fuco1 thank you :) As far as -defun were you thinking of how to implement this using dash functions? Sorry if I wasn't clear; I have the implementation working for this already (although it could be simplified) and the OP was meaning to ask if this would be appropriate to include in dash.

ndwarshuis avatar Jan 12 '20 16:01 ndwarshuis

I think it is appropriate, but I would rename &key to &plist to correspond with -lambda signature. That's why I mentioned implementing it via -lambda which will keep it consistent (but possibly introduce other problems).

Also there's the version of @alphapapa. The best would be to somehow merge the solutions so each use-case can be expressed.

Fuco1 avatar Jan 12 '20 17:01 Fuco1

Ok got it. I can work on a PR and also try and merge the behavior from #268.

ndwarshuis avatar Jan 15 '20 22:01 ndwarshuis