Lush/Macros

A macro is a meta-function that returns some code to be run. To the user of a macro, a macro looks and behaves exactly like a function. Indeed most users of Lush can become fairly fluent with its built-in libraries without concerning themselves with which functions are actually macros.

Why write a macro?
Macros are more tedious to write than functions, and harder to debug. However, they offer advantages that make them worthwhile in some cases:


 * Argument type flexibility in compiled code Often one wants to write a compilable function that can take any numeric type or idx as an argument. In compiled lush, you need to restrict each function to taking one specific type. Macro functions need not restrict themselves in that way, so long as the generated code compiles with the given argument types.
 * Metaprogramming: Sometimes we end up writing many functions whose bodies are nearly identical except for a few isolated differences. A single macro can expand into these different functions depending on read-time traits of its arguments.
 * Variable-length argument lists: Macros can have variable-length argument lists. This can be used as a compilable alternative to regular functions with  arguments, which are not compilable.

Limitations of compiled macros
When compiling expressions that compile macros, the macro is deprived of much information that is only available at runtime. This limits compiled macros from using any kind of runtime information, such as:


 * Argument type Type information, such as the numerical type of an idx, cannot be gleaned from the arguments of a macro at compile-time (macro arguments will typically have the generic symbol type ).
 * Tensor dimensionality Unlike in some other programming languages, lush tensors of different dimensionality (e.g. vectors vs matrices) are all of the same type, . Thus having a macro make compile-time decisions based on idx dimension is a sure loser.

So what can you switch on in a compilable macro? Any value known at compile time, such as:
 * Literals You can have a macro make compile-time decisions based on an  argument, as long as the argument is suppied as a literal, rather than the result of a computed expression.
 * Number of arguments See below for how to implement variable-length argument lists.

How to write a macro
Macros are functions that are evaluated at read-time, and return function bodies. Consider this dirt-simple non-macro function: A macro equivalent could be written as follows: As you can see, macro functions are declared using dm rather than de. Also, they have an additional first argument,, that holds the name of the macro. Finally, they don't calculate the answer of the function, they calculate an expression that, when evaluated, returns the function body. In the macro above,  will evaluate out to   at read-time.

The following will show you the basic changes you need to apply in order to mechanically transform a function into a macro. (Somebody should write a macro that does this!):

Replace lists with list-generators
Any lush expression is a list of elements in parentheses: (a b c d) In the macro version of the function, all such lists should be changed into list-generators: (list a b c d) This rule should be applied recursively to each of the list elements. If the element is a list, replace it with a list-generator. Otherwise, quote it with a leading  so that it gets returned as-is without being evaluated. ; normal code (sum (range 5)) ; macro code (list 'sum (list 'range 5))

Quote all non-argument symbols
Notice in the example above,  and   are preceded by an apostrophe. This prevents the interpreter from evaluating the value of the symbol " " at read-time and changing it to " . Likewise, symbols such as operators (" "), class names (" ") and non-argument variables all need to be "quoted" with an apostrophe so they make it into the generated function body as-is.

Below are some examples of quoting: ; normal code: (+ 1 2 3) ; macro code (don't need to quote numeric literals): ('+ 1 2 3) ; normal code (embedded C code): ; macro code: '#{ exit(0); #} ; normal code (==> my-object some-method) ; macro code (list '==> 'my-object 'some-method) ; normal code (:my-object:some-slot) ; macro code (expands the ':' shorthand into the 'scope' function that it represents). (list 'scope 'my-object 'some-slot) Note that literals such as the numeric literals,  , and   above, or string literals such as  , need not be quoted.
 * 1) { exit(0); #}

Put multi-line functions in an enclosing list
(Write about using list, progn, or let).

Use argument variables at most once
The  example above is a naive implementation, because it uses the arguments directly. The problem with this is that every time  or   is used in the macro, is is evaluated. For example, if the user plugs in  into the   slot, that function will be called each time   appears in the macro. This is fine in a simple macro such as, where each argument is used just once, but most functions refer to their arguments many times. An easy work-around is to assign the arguments to local variables at the beginning of the macro: Getting into the habit of enclosing your macro body with  has the additional benefit of making sure all your macro lines get returned, not just the last line (see previous section).

Debugging Macros
Of the three kinds of code you can write in lush (interpreted, compiled, and macro), macro code is arguably the hardest to debug, since a bug could be in the read-time code generation or in the generated run-time code. When debugging the run-time code you can use the same functions used in debugging normal functions, only "macroized" as described earlier. In other words,  becomes   and   becomes , etc. When debugging the read-time generation, you can use the following tools:

(macro-expand)
The first thing you should do after coding up your macro is to view the code it generates. Use, which formats and prints out the code generated by a macro expression.

Example:

(print)
You can also insert  statements to print out the compile-time value of certain expressions, so long as you're careful that these   statements don't change the value of the macro-expansion.