Lisp - Macro Writing Style



Macro is very powerful tool in LISP programming language and can be used to extend the capability of the language. It is often used in metaprogramming. So it is very crucial to write code which is readable, maintainable and highly reliable. In this chapter, we're discussing the best practices of writing macros.

Hygiene

Use gensym

While writing macros, when a code is expanded, a variable declared within macro may override the surrounding variable causing unintentional variable capturing and may result in wrong computation. Using gensym, we can create unique variables within a macro so that macro result is always predictable.

Use with-gensyms

Using with-gensyms, we can create multiple unique symbols.

And we should avoid symbols which can clash on expanding the code.

Clear and Readable Code

  • Use defmacro Judiciously − Macro is a very powerful but writing macro can be very complex. Define a macro only when needed. Define a macro, when we need to control the code evaluation or to create new LISP forms.

  • Write concise code − A macro code should perform a well defined transformation. In case of complex logic, break the code into helper functions.

  • Use meaningful names − Use descriptive, meaningful names for the macro and its arguments, and its variables.

  • Format Code − A consistent indentation and spacing always helps in increasing readablilty of the code.

Use Quasiquotations

Back Quote (`), Comma (,) and Comma-at (,@)

Back Quote (`), Comma (,) and Comma-at (,@) are powerful tools to create effective templates, to create complex LISP forms. We can use them in macros to make them more readable and maintable.

A backquoted template should be written to visual confirm that the expanded code is producing the correct form of code required.

Do thorough testing

A macro should be tested thoroughly with multiple inputs. Cover edge cases. As macro produces code, testing is very crucial.

Use macroexpand or macroexpand-1 functions. These functions allows to inspect the code generated which is very valuable in debugging the code.

main.lisp

; define a macro to swap two numbers 
(defmacro swap (x y)
   (let ((temp (gensym))) ; create a unique variable
      `(let ((,temp ,x))  ; insert the unique variable and value of x
         (setf ,x ,y)
         (setf ,y ,temp))))
     
(print(macroexpand '(swap temp my-var))) 

Output

When you execute the code, it returns the following result −

(LET ((#:G2949 TEMP)) (SETF TEMP MY-VAR) (SETF MY-VAR #:G2949)) 

Documentation

Document the macro to explaing what is the intended behavior, how it works, any issue. Add examples of using the macro.

Important Considerations

  • Consider Side Effects− A macro should be side-effect free. A macro should transform code instead of performing any computation.

  • Account Evaluation Order− A macro can alter the code evaluation order. It is good to understand how a macro affect the code evalutation.

Advertisements