I was having a conversation with a colleague recently and tried telling him about the beauty of (Common) Lisp. I tried to explain macros somehow, since I consider macros one of the killer features of Lisp, but I failed rather miserably -- I couldn't find a good example which would be short, concise and understandable by a "mere mortal" programmer (decade of Java experience, a bright guy altogether, but very little experience with "higher-order" languages).
How would you explain Lisp macros by example if you had to?
that takes arguments and returns a LISP form to be evaluated. It is useful when the same code has to be executed with a few variable changes. For example, rather than writing the same code for squaring a number, we can define a macro that holds the code for squaring.
The Common Lisp macro facility allows the user to define arbitrary functions that convert certain Lisp forms into different forms before evaluating or compiling them. This is done at the expression level, not at the character-string level as in most other languages.
A macro is an ordinary piece of Lisp code that operates on another piece of putative Lisp code, translating it into (a version closer to) executable Lisp.
Macros will be used in Lisp code. During a macroexpansion phase, the Lisp expression will be passed to the macro function. This macro function can do arbitrary computation at macroexpansion time. The result of this call has to be again Lisp code.
From my experience, macros make the best impression on people when they see how it helps to produce code, that cannot be made by the procedures or other constructs. Very often such things may be described as:
<common code>
<specific code>
<other common code>
where <common code>
is always the same. Here are some examples of such schema:
1. The time
macro. Code in a language without macros will look something like this:
int startTime = getCurrentTime();
<actual code>
int endTime = getCurrentTime();
int runningTime = endTime - startTime;
You cannot put all common code to procedure, since it wraps actual code around. (OK, you can make a procedure and pass actual code in lambda function, if the language supports it, but it is not always convenient).
And, as you most probably know, in Lisp you just create time
macro and pass actual code to it:
(time
<actual code>)
2. Transactions. Ask Java-programmer to write method for simple SELECT
with JDBC - it will take 14-17 lines and include code to open connection and transaction, to close them, several nested try-catch-finally
statements and only 1 or 2 lines of unique code.
In Lisp you just write with-connection
macro and reduce code to 2-3 lines.
3. Synchronization. OK, Java, C# and most of the modern languages already have statements for it, but what to do if your language doesn't have such a construct? Or if you want to introduce new kind of synchronization like STM-based transactions? Again, you should write separate class for this task and work with it manually, i.e. put common code around each statement you want to synchronize.
That was only few examples. You can mention "not-to-forget" macros like with-open
series, that clean-up environment and protect you from recourses leaks, new constructs macros like cond
instead of multiple if
s, and, of course, don't forget about lazy constructs like if
, or
and and
, that do not evaluate their arguments (in opposite to procedure application).
Some programmers may advocate, that their language has a technology to treat this or that case (ORM, AOP, etc), but ask them, would all these technologies be needed, if macros existed?
So, taking it altogether and answering original question about how to explain macros. Take any widely-used code in Java (C#, C++, etc), transform it to Lisp and then rewrite it as a macro.
New WHILE statement
Your language designer forgot a WHILE statement. You've mailed him several times. No success. You've waited from language version 2.5, 2.6 to 3.0. Nothing happened...
In Lisp:
(defmacro while ... insert your while implementation here ...)
Done.
The trivial implementation using LOOP takes a minute.
Code generation from specifications
Then you may want to parse call detail records (CDRs). You have record names with field descriptions. Now I can write classes and methods for each of those. I could also invent some configuration format, parse a configuration file and create the classes. In Lisp I would write a macro that generates the code from a compact description.
See Domain Specific Languages in Lisp, a screencast showing a typical development cycle from a working sketch to a simple macro based generalization.
Code rewriting
Imagine that you have to access slots of objects using getter functions. Now imagine that you need to access some objects multiple times in some code region. For some reason using temporary variables is no solution.
...
... (database-last-user database) ...
...
Now you could write a macro WITH-GETTER which introduces a symbol for the getter expression.
(with-getters (database (last-user database-last-user))
...
... last-user
...)
The macro would rewrite the source inside the enclosed block and replace all specified symbols with the getter expression.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With