Dean Chen
Dean Chen

Reputation: 3890

Lisp macro special usage?

I read Hackers and Painters book recently. The author said the Lisp macro is very powerful and special than other language's macro.

I began to learn macro of ELisp,but still don't see what special usage of it? Can't see the difference between it and C/C++ macro.

Could anybody explain it for me?

Upvotes: 3

Views: 701

Answers (3)

habrewning
habrewning

Reputation: 1069

Short Anwser

Lisp macros provide a mechanism to extend the programming language. See the following example. Everybody in C++ understands this:

for(int i = 0;i<10;i++){
  something();
}

Everybody can also understand this:

do_n_times(10){
  something();
}

We understand this, so these examples would be actually good language constructs in certain situations. But these examples are not legal in C++. It is not possible to achieve such things in C++. It would be nice to be able to extend the programming language in this way. With Lisp macros you can do such things.

More Examples

Let's see a practical example. You have a class with methods addButton and updateLayout. There is a rule that whenever you add a button, you must always call updateLayout. It is error prone to require from the user of your class not to forget to call updateLayout. So you may want to give him a way to addButtons where updateLayout is automatically called.

The solution is this:

template <typename F>
   void layoutChanges(F codeBlock){
   codeBlock();
   updateLayout();
}

....

my_object->layoutChanges([&](){
        my_object->addButton(new Button());
        my_object->addButton(new Button());
        my_object->addButton(new Button());
        my_object->addButton(new Button());
});

That works, but the syntax is laborious. You may prefer such a syntax:

my_object->layoutChanges(){
        my_object->addButton(new Button());
        my_object->addButton(new Button());
        my_object->addButton(new Button());
        my_object->addButton(new Button());
}

But that is not C++. If you had Lisp macros, you could make it legal.

If these examples are too academic, see the following Python code:

with open('data.txt', 'r') as f:
    data = f.read()

The file is automatically closed, when f.read() is completed. Such automatic mechanisms are very helpful. It makes the code easyer to read, shorter and safer. So it would be nice to have such things also in C++. But again, in C++ you cannot create your own language constructs. This is not possible:

with_open_file(f : "data.txt"){
    data = f.read();
}

So you always have to find workarounds. The macro mechanism of Lisp makes such things possible.

Upvotes: -1

danlei
danlei

Reputation: 14291

I think the most important points are:

  • Lisp code is represented as lists, and with Lisp macros, you can use all of Lisp's list processing functionality (which is something Lisp is well suited for, remember that LISP stands for LISt Processing) to transform it. (Homoiconicity) C/C++ preprocessor macros, however, are mere textual substitutions. That is, to perform more complex transformations, you would have to parse the input to a C/C++ preprocessor macro yourself (and you usually don't have access to the parser in those languages). You can't use all of C/C++ or whatever the language at hand to transform your code. These simple macro facilities seem to be more suited for little abbreviations (or bypassing the type system, performance workarounds, etc.), than for real abstraction. (Not that one couldn't use them for more complex tasks, but it quickly becomes a mess.)

  • Lisp syntax is also much more regular than the usual curly brace languages. You don't have to wory about precedence rules, infix operators, or the distinction between statements and expressions.

  • In contrast to Lisp macros, as far as I know, with C/C++ preprocessor macros, you can't easily use macros within macro definitions without jumping through hoops, because the preprocessor makes only one pass.

  • There is no easy way (that I know about) to introduce fresh symbols in C/C++ style preprocessor macros. (You can use gensym and abstractions built around it like with-gensyms etc. in CL style macros, and there also are hygienic macro systems, mostly used in the Scheme dialects.)

  • Backquoted expressions in Lisp are a really convenient fill-in-the-blanks approach to build the code a macro expands to.

All in all, metaprogramming with Lisp macros is far more convenient than approaches with the C/C++ preprocessor. In practice, this means, that even a relative newcomer to Lisp would be able to whip up his own looping constructs or other control structures without much hassle. With a little more experience, writing more complex DSLs also becomes relatively easy. Compare this to the C/C++ preprocessor, where these tasks would probably be considered to be some kind of black magic, reserved for only the bravest.

As a quick example, try writing something like this with the C/C++ preprocessor:

(defmacro bif ((var expression) then &optional else)
  `(let ((,var ,expression))
     (if ,var ,then ,else)))

It introduces a new control structure bif ("binding if") which evaluates an expression, binds it to a given symbol, and then conditionally executes the then or else branch with the binding in scope. It nests properly and works as expected. Now, things like this can be written by any Lisp programmer worth their salt in no time. Since it's so easy, Lisp programmers usually don't hesitate when they feel the need for a new construct – the barrier between language designer and language user is blurred.

(Even if one manages to write this using the C/C++ preprocessor, the next step, i.e. writing something like Lisp's cond, or a new looping construct, would get much more complex quickly.)

Upvotes: 4

Ricky Morse
Ricky Morse

Reputation: 488

Lisp macros allow you to modify the language at a deeper level than C++ macros. Lisp macros are pieces of code that are executed during the compilation phase, and they can control the evaluation of their parameters.

As an example, Lisp is based on ~25 "special forms" that the compiler has to handle. The rest of Lisp can be iterated into existence on top of these special forms. I'm not as familiar with ELisp, but the Common Lisp loop functionality is implemented via a macro. You can extend the language -- often Lisps only have one special form for conditionals, and all of the others are implemented, in Lisp, on top of that one. defun, which is the structure used to define a function, can be implemented as a macro, I believe.

C++ macros, although powerful, are executed by a preprocessor, which means that they are limited in what they can do. Lisp macros, since they are executing in the compilation environment, can execute functions to determine how to rewrite their input, including functions defined by the programmer.

This response is rather complicated and vague, so hopefully someone with more time will be able to provide a better one. I apologize as Pascal did, for making this answer long and vague.

Upvotes: 3

Related Questions