I don’t create many lisp macros but it is still a huge advantage having them. I’m a big user of macros that other people have created. For example, by default emacs lisp does not support keyword parameters. Thanks to the magic of macros they have been added in the cl.el common lisp package.

( require ' cl ) ( defun* example ( &rest args &key (a 1) (b 2)) (format "%s %s %s" args a b))

(example) works as expected and returns "nil 1 2"

(example :a 10) is pretty good too although you may be surprised that keywords are not removed from the &rest args "(:a 10) 10 2"

However, (example 1) returns the following surprise (error "Keyword argument 1 not one of (:a :b)") .

If you are using &rest and &key together, you can fix this with &allow-other-keys .

( defun* example ( &rest args &key (a 1) (b 2) &allow-other-keys ) (format "%s %s %s" args a b))

Okay, so much for pithy examples. How about something practical?

( defun* remote-path ( &rest path &key (user "jared" ) (server "someserver" ) &allow-other-keys ) (concat "ftp:" user "@" server ":" (mapconcat #'identity path "/" )))

(remote-path :user "uat-user" "/tmp" "test-dir") throws this error (wrong-type-argument sequencep :user) . This is because if you use &rest arguments and &key arguments together, the keyword arguments are also passed through into &rest .

I couldn’t find an obvious function to remove keyword parameters so I’ve written my own remove-keyword-params .

( defun remove-keyword-params (seq) ( if (null seq) nil ( let ((head (car seq)) (tail (cdr seq))) ( if (keywordp head) (remove-keyword-params (cdr tail)) (cons head (remove-keyword-params tail)))))) ( defun* remote-path ( &rest path &key (user "jared" ) (server "someserver" ) &allow-other-keys ) (concat "ftp:" user "@" server ":" (mapconcat #'identity (remove-keyword-params path) "/" )))

Now it works correctly.

(remote-path :user "uat-user" "/tmp" "test-dir" ) ;; "ftp:uat-user@someserver:/tmp/test-dir"

So what do I want this for? For my directory aliases of course.

( defconst *dired-dirs* (list (cons "dev" (remote-path "~/dev" )) (cons "dev:system" (remote-path :user "dev-user" "~/system" )) (cons "uat:system" (remote-path :user "dev-user" "~/system" ))))

And surprise surprise, because emacs is architected so well, it all works remotely through tramp completely transparently.