Search code examples
macroslispelispquote

Why not allow quoted arguments in macros


I've asked this question recentely on reddit but I thought perhaps I'd get a better answer here.

Often macros I write take either a symbol or a list of symbols and sometimes these symbols represent a variable or a function. And in these cases I'm tempted write the macros so that their arguments can be quoted or sharquoted. Here's a concrete example:

In emacs lisp the function add-hook adds HOOK-FN to a list named HOOK.

(add-hook 'hook #'hook-fn)

Often you want to add several items to HOOK or you want HOOK-FN to several hooks, but add-hook only takes in one hook or one hook-fn at a time. So, I made a macro for this called add-hook! which can accept multiple arguments.

(add-hook! hook-name (fn1 fn2 fn3))

;; or

(add-hook! (hook-name1 hook-name2) hook-fn)

I am inclined to allow arugments to the macro to be quoted or sharpquoted. Just as they would be in a function call.

(add-hook! 'hook-name #'fn)

To do this I'd use a function like this to strip off the quotes from each argument.

(defun unquote (form)
  "Unquote a form if it is quoted, otherwise return form."
  (if (member (car-safe it) '(quote function))
      (cadr form)
    form))

I am inclided to do this because quoting and sharpquoting (1) makes it clear whether I mean a function or a symbol and (2) triggers my autocompletion, which helps me type the function or symbol faster.

However, I am under the impression that this is not the convention in lisp. Why? Are there good reasons why you should not allow quoted arguments in macros?


Solution

  • You certainly can do that, but it looks a little strange, because it mixes visual signals that say “Function here, normal evaluation” and “Macro here, special evaluation”.

    Why don't you make add-hook-fns a (quite simple) function, just like add-hook is? Then all the quoting naturally falls into place and is even required.

    (defun add-hook-fns (hook fns)
      (dolist (fn fns)
        (add-hook hook fn)))
    

    This would then look on invocation like this:

    (add-hook-fns 'some-hook (list #'foo #'bar #'baz))
    

    If you don't want that explicit call to list in there, you can use a macro to eliminate just that:

    (defmacro add-hook-fns* (hook fns)
      `(add-hook-fns ,hook (list ,@fns)))
    

    Et voilà:

    (add-hook-fns* 'some-hook (#'foo #'bar #'baz))
    

    However, I don't recommend that, see reasons at the start.

    Instead, I'd still use just a function, but with rest parameters:

    (defun add-hook-fns (hook &rest fns)
      (dolist (fn fns)
        (add-hook hook fn)))
    

    Now:

    (add-hook-fns 'some-hook #'foo #'bar #'baz)