Skip to content

Instantly share code, notes, and snippets.

@nimaai
Last active October 12, 2015 21:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nimaai/2acc76d86e2539e088e1 to your computer and use it in GitHub Desktop.
Save nimaai/2acc76d86e2539e088e1 to your computer and use it in GitHub Desktop.
Lisp macros guideline

Lisp macros guidelines

Summary of and excerpts from [chapter 8 of "On Lisp"] (http://www.bookshelf.jp/texi/onlisp/onlisp_9.html#SEC61). All examples are in Common Lisp.

###No other option except macro

Macros can do two things that functions can't:

  1. they can control (or prevent) the evaluation of their arguments.
  2. they are expanded right into the calling context (inline expansion).

####Control (prevention) of arguments' evaluation

  1. Transformation. The expansion goes beyond simple templating and is determined by the content resp. structure of its arguments, which are then transformed accordingly. Example: (macroexpand-1 '(setf (car x) 'a)) => (progn (rplaca x 'a) 'a).
  2. Binding. Any new operator which is to alter the lexical bindings of its arguments must be written as a macro. The first argument to setq is not evaluated, for example, so anything built on setq must be a macro which expands into a setq, rather than a function which calls it.
  3. Conditional evaluation. All the arguments to a function are evaluated. In constructs like when, we want some arguments to be evaluated only under certain conditions.
  4. Multiple evaluation. Not only are the arguments to a function all evaluated, they are all evaluated exactly once. We need a macro to define a construct like do, where certain arguments are to be evaluated repeatedly.

####Inline expansion

  1. Using the calling environment. A macro can generate an expansion containing a variable whose binding comes from the context of the macro call. It's useful mostly in the continuation-passing macros. Normally, the preferred way to communicate with a macro is through its parameters.
  2. Wrapping a new environment. A macro can cause its arguments to be evaluated in a new lexical environment. The classic example is let, which could be implemented as a macro on lambda.
  3. Saving function calls. The third consequence of the inline insertion of macroexpansions is that in compiled code there is no overhead associated with a macro call. By runtime, the macro call has been replaced by its expansion. (The same is true in principle of functions declared inline.)

###Macro or function?

(defun avg (&rest args)
  (/ (apply #'+ args) (length args)))
(defmacro avg (&rest args)
  (/ (+ ,@args) ,(length args)))

The function version entails an unnecessary call to length each time avg is called. At compile-time we may not know the values of the arguments, but we do know how many there are, so the call to length could just as well be made then.

####The pros

  1. Computation at compile-time. Every bit of computation which can be done at compile-time is one bit that won't slow the program down when it's running. If an operator could be written to do some of its work in the macroexpansion stage, it will be more efficient to make it a macro, because whatever work a smart compiler can't do itself, a function has to do at runtime.
  2. Integration with Lisp. Instead of writing a program to solve a certain problem, you may be able to use macros to transform the problem into one that Lisp already knows how to solve. This approach, when possible, will usually make programs both smaller and more efficient. It is applicable mostly to embedded languages.
  3. Saving function calls. A macro call is expanded right into the code where it appears. So if you write some frequently used piece of code as a macro, you can save a function call every time it's used. This job is supposed to be taken over by functions declared inline.

####The cons

  1. Functions are data, while macros are more like instructions to the compiler. Functions can be passed as arguments (e.g. to apply),returned by functions, or stored in data structures. None of these things are possible with macros. In some cases, you can get what you want by enclosing the macro call within a lambda-expression.
  2. Clarity of source code. Macro definitions can be harder to read than the equivalent function definitions. So if writing something as a macro would only make a program marginally better, it might be better to use a function instead.
  3. Clarity at runtime. Macros are sometimes harder to debug than functions. If you get a runtime error in code which contains a lot of macro calls, the code you see in the backtrace could consist of the expansions of all those macro calls, and may bear little resemblance to the code you originally wrote. Macros disappear when expanded!
  4. Recursion. Using recursion in macros is not so simple as it is in functions. Although the expansion function of a macro may be recursive, the expansion itself may not be.

Finally, it should be noted that clarity at runtime (point 3) rarely becomes an issue. Debugging code which uses a lot of macros will not be as difficult as you might expect. Macro definitions and utilities in general, tend to be written in small, trusted layers. Generally their definitions are less than 15 lines long.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment