LFE Style Guide

8 Meta-Language Guidelines

8.1 Macros

Macros bring syntactic abstraction, which is a wonderful thing. It helps make your code clearer, by describing your intent without getting bogged in implementation details (indeed abstracting those details away). It helps make your code more concise and more readable, by eliminating both redundancy and irrelevant details. But it comes at a cost to the reader, which is learning a new syntactic concept for each macro. And so it should not be abused.

The general conclusion is that there shouldn't be any recognizable design pattern in a good LFE program. The one and only pattern is: use the language, which includes defining and using syntactic abstractions.

Existing macros should be used whenever they make code clearer by conveying intent in a more concise way, which is often. When a macro is available in your project that expresses the concept you're using, you should not write the expansion rather than use the macro.

New macros should be defined as appropriate, which should be seldom, for common macros have already been provided by the language and its various libraries, and your program typically only needs few new ones relative to its size.

You should follow the OAOOM rule of thumb for deciding when to create a new abstraction, whether syntactic or not: if a particular pattern is used more than twice, it should probably be abstracted away. A more refined rule to decide when to use abstraction should take into account the benefit in term of number of uses and gain at each use, to the costs in term of having to get used to reading the code. For syntactic abstractions, costs and benefits to the reader is usually more important than costs and benefits to the writer, because good code is usually written once and read many times by many people (including the same programmer who has to maintain the program after having forgotten it). Yet the cost to the writer of the macro should also be taken into account; however, in doing so it should rather be compared to the cost of the programmer writing other code instead that may have higher benefits.

Using Lisp macros properly requires taste. Avoid writing complicated macros unless the benefit clearly outweighs the cost. It takes more effort for your fellow developers to learn your macro, so you should only use a macro if the gain in expressiveness is big enough to justify that cost. As usual, feel free to consult your colleagues if you're not sure, since without a lot of Lisp experience, it can be hard to make this judgment.

You should never use a macro where a function will do. That is, if the semantics of what you are writing conforms to the semantics of a function, then you should write it as a function rather than a macro.

When you write a macro-defining macro (a macro that generates macros), document and comment it particularly clearly, since these are harder to understand.

If your macro has a parameter that is a Lisp form that will be evaluated when the expanded code is run, you should name the parameter with the suffix -form. This convention helps make it clearer to the macro's user which parameters are Lisp forms to be evaluated, and which are not. The common names body and end are exceptions to this rule.

You should follow the so-called CALL-WITH style when it applies. This style is explained at length here. The general principle is that the macro is strictly limited to processing the syntax, and as much of the semantics as possible is kept in normal functions. Therefore, a macro WITH-FOO is often limited to generating a call to an auxiliary function CALL-WITH-FOO with arguments deduced from the macro arguments. Macro body arguments are typically wrapped into a lambda expression of which they become the body, which is passed as one of the arguments of the auxiliary function.

The separation of syntactic and semantic concerns is a general principle of style that applies beyond the case of WITH- macros. Its advantages are many. By keeping semantics outside the macro, the macro is made simpler, easier to get right, and less subject to change, which makes it easier to develop and maintain. The semantics is written in a simpler language — one without staging — which also makes it easier to develop and maintain. It becomes possible to debug and update the semantic function without having to recompile all clients of the macro. The semantic function appears in the stack trace which also helps debug client functions. The macro expansion is made shorter and each expansion shares more code with other expansions, which reduces memory pressure which in turn usually makes things faster. It also makes sense to write the semantic functions first, and write the macros last as syntactic sugar on top. You should use this style unless the macro is used in tight loops where performance matters; and even then, see our rules regarding optimization.

Any functions (closures) created by the macro should be named, which can be done using FLET.

When you write a macro with a body, such as a WITH-xxx macro, even if there aren't any parameters, you should leave space for them anyway. For example, if you invent WITH-LIGHTS-ON, do not make the call to it look like (defmacro with-lights-on (body b) ...). Instead, do (defmacro with-lights-on (() body b) ...). That way, if parameters are needed in the future, you can add them without necessarily having to change all the uses of the macro.

8.2 EVAL-WHEN-COMPILE

TBD

8.3 EVAL

Places where it is actually appropriate to use EVAL are so few and far between that you should consult with your reviewers; it's easily misused.

Often, what you really need is to write a macro, not to use EVAL.

You may be tempted to use EVAL as a shortcut to evaluating expressions in a safe subset of the language. But it often requires more scrutiny to properly check and sanitize all possible inputs to such use of EVAL than to build a special-purpose evaluator. You must not use EVAL in this way at runtime.

Places where it is OK to use EVAL are:

  • The implementation of an interactive development tool.
  • The build infrastructure.
  • Backdoors that are part of testing frameworks. (You should NOT have such backdoors in production code.)
  • Macros that fold constants at compile-time.
  • Macros that register definitions to meta-data structures; the registration form is sometimes evaluated at compile-time as well as included in the macro-expansion, so it is immediately available to other macros.

Note that in the latter case, if the macro isn't going to be used at the top- level, it might not be possible to make these definitions available as part of the expansion.