Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request] defun with &rest and &key #329

Open
ndwarshuis opened this issue Jan 10, 2020 · 5 comments
Open

[Feature request] defun with &rest and &key #329

ndwarshuis opened this issue Jan 10, 2020 · 5 comments

Comments

@ndwarshuis
Copy link

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.

@Fuco1
Copy link
Collaborator

Fuco1 commented Jan 10, 2020

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

@alphapapa
Copy link

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

@ndwarshuis
Copy link
Author

@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.

@Fuco1
Copy link
Collaborator

Fuco1 commented Jan 12, 2020

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.

@ndwarshuis
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants