dash.el
dash.el copied to clipboard
-defun macro to flexibly handle arguments (better than cl-defun)
Wanting more flexible argument handling, I tried to implement this with pcase
, but I couldn't get it to match the plist at the end. It was much easier with -let
, however I ran into a problem that I'm not sure how to overcome, or if it's even possible to do so.
(defmacro -defun (name arg-patterns &rest body)
(declare (indent defun))
(-let ((body-sym (gensym))
((arg-patterns defaults) (cl-loop with patterns with defaults
for pattern = (pop arg-patterns)
until (eq pattern :defaults)
collect pattern into patterns
finally return (list patterns (car arg-patterns)))))
`(defun ,name (&rest args)
(let ((,body-sym (lambda ()
,@body))
,@defaults)
(or ,@(--map `(-when-let (,it args) (funcall ,body-sym))
arg-patterns)
(user-error ,(concat (symbol-name name) ": Called with invalid args: %s") args))))))
(-defun nuts ((type number &keys :salted salted)
(type &keys :salted salted)
(type number)
(type)
:defaults ((number 10) (salted t)))
(list :type type :number number :salted salted))
(nuts 'peanuts) ;; => (:type peanuts :number 10 :salted t)
(nuts 'peanuts 20) ;; => (:type peanuts :number 20 :salted t)
(nuts 'peanuts 20 :salted t) ;; => (:type peanuts :number 20 :salted t)
That all works great, but when I try to set :salted
to nil
, it doesn't work, because -let
doesn't match the plist key's nil
value:
(nuts 'peanuts :salted nil) ;; => (:type peanuts :number 10 :salted t)
If this could be fixed, this could be a really useful way to define functions that accept arguments. It would allow one to avoid manually looping over argument lists.
Any ideas would be appreciated. :)
In case anyone is interested, here's my aborted attempt using pcase
:
(defmacro pcase-lambda* (arg-patterns &rest body)
(declare (indent defun))
(let ((body-sym (gensym)))
`(lambda (&rest args)
(let ((,body-sym (lambda ()
,@body)))
(pcase args
,@(--map `(,it (funcall ,body-sym))
arg-patterns)
(_ (user-error "Called with invalid args")))))))
(setq argh (pcase-lambda* (`(,files ,pred . ,(map something)))
;; TODO: default values
(list :files files :pred pred :something something)))
(funcall argh 'files 'pred 'something 'something) ;; => (:files files :pred pred :something nil)