[Feature request] defun with &rest and &key
ndwarshuis opened this issue · 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.
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 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.
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.
Ok got it. I can work on a PR and also try and merge the behavior from #268.