views:

1686

answers:

8

Reading Paul Graham's essays on programming languages one would think that Lisp macros are the only way to go. As a busy developer working on other platforms I have not had the privledge of using lisp macros. As someone who wants to understand the buzz please explain what make's this feature so powerful.

Please also relate this to something I would understand from the world of python, java, c#, c development.

+11  A: 

You will find a comprehensive debate around lisp macro here.

An interesting subset of that article:

In most programming languages, syntax is complex. Macros have to take apart program syntax, analyze it, and reassemble it. They do not have access to the program's parser, so they have to depend on heuristics and best-guesses. Sometimes their cut-rate analysis is wrong, and then they break.

But Lisp is different. Lisp macros do have access to the parser, and it is a really simple parser. A Lisp macro is not handed a string, but a preparsed piece of source code in the form of a list, because the source of a Lisp program is not a string; it is a list. And Lisp programs are really good at taking apart lists and putting them back together. They do this reliably, every day.

Here is an extended example. Lisp has a macro, called "setf", that performs assignment. The simplest form of setf is

    (setf x whatever)

which sets the value of the symbol "x" to the value of the expression "whatever".

Lisp also has lists; you can use the "car" and "cdr" functions to get the first element of a list or the rest of the list, respectively.

Now what if you want to replace the first element of a list with a new value? There is a standard function for doing that, and incredibly, its name is even worse than "car". It is "rplaca". But you do not have to remember "rplaca", because you can write

    (setf (car somelist) whatever)

to set the car of somelist.

What is really happening here is that "setf" is a macro. At compile time, it examines its arguments, and it sees that the first one has the form (car SOMETHING). It says to itself "Oh, the programmer is trying to set the car of somthing. The function to use for that is 'rplaca'." And it quietly rewrites the code in place to:

    (rplaca somelist whatever)
VonC
+9  A: 

Lisp macros allow you to decide when (if at all) any part or expression will be evaluated. To put a simple example, think of C's:

expr1 && expr2 && expr3 ...

What this says is: Evaluate expr1, and, should it be true, evaluate expr2, etc.

Now try to make this && into a function... thats right, you can't. Calling something like:

and(expr1, expr2, expr3)

Will evaluate all three exprs before yielding an answer regardless of whether expr1 was false!

With lisp macros you can code something like:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

now you have an &&, which you can call just like a function and it won't evaluate any forms you pass to it unless they are all true.

To see how this is useful, contrast:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

and:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

Other things you can do with macros are creating new keywords and/or mini-languages (check out the (loop ...) macro for an example), integrating other languages into lisp, for example, you could write a macro that lets you say something like:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

And thats not even getting into Reader macros.

Hope this helps.

dsm
I think it should be: "(apply this and might be wrong!"
Svante
@svante - on two counts: first, apply only works on functions. second, apply take a list of arguments to pass, so you want one of "(funcall fn ,@exprs)", "(apply fn (list ,@exprs)" or "(apply fn ,@exprs nil)", not "(apply fn ,@exprs)".
Aaron
+15  A: 

Common Lisp macros essentially extend the "syntactic primitives" of your code.

For example, in C, the switch/case construct only works with integral types and if you want to use it for floats or strings, you are left with nested if statements and explicit comparisons. There's also no way you can write a C macro to do the job for you.

But, since a lisp macro is (essentially) a lisp program that takes snippets of code as input and returns code to replace the "invocation" of the macro, you can extend your "primitives" repertoire as far as you want, usually ending up with a more readable program.

To do the same in C, you would have to write a custom pre-processor that eats your initial (not-quite-C) source and spits out something that a C compiler can understand. It's not a wrong way to go about it, but it's not necessarily the easiest.

Vatine
+3  A: 

A lisp macro takes a program fragment as input. This program fragment is represented a data structure which can be manipulated and transformed any way you like. In the end the macro outputs another program fragment, and this fragment is what is executed at runtime.

C# does not have a macro facility, however an equivalent would be if the compiler parsed the code into a CodeDOM-tree, and passed that to a method, which transformed this into another CodeDOM, which is then compiled into IL.

This could be used to implement "sugar" syntax like the for each-statement using-clause, linq select-expressions and so on, as macros that transforms into the underlying code.

If Java had macros, you could implement Linq syntax in Java, without needing Sun to change the base language.

Here is pseudo-code for how a lisp-style macro in C# for implemening using could look:

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }
JacquesB
+1  A: 

Think of what you can do in C or C++ with macros and templates. They're very useful tools for managing repetitive code, but they're limited in quite severe ways.

  • Limited macro/template syntax restricts their use. For example, you can't write a template which expands to something other than a class or a function. Macros and templates can't easily maintain internal data.
  • The complex, very irregular syntax of C and C++ makes it difficult to write very general macros.

Lisp and Lisp macros solve these problems.

  • Lisp macros are written in Lisp. You have the full power of Lisp to write the macro.
  • Lisp has a very regular syntax.

Talk to anyone that's mastered C++ and ask them how long they spent learning all the template fudgery they need to do template metaprogramming. Or all the crazy tricks in (excellent) books like Modern C++ Design, which are still tough to debug and (in practice) non-portable between real-world compilers even though the language has been standardised for a decade. All of that melts away if the langauge you use for metaprogramming is the same language you use for programming!

Matt Curtis
+3  A: 

I'm not sure I can add some insight to everyone's (excellent) posts, but...

Lisp macros work great because of the Lisp syntax nature.

Lisp is an extremely regular language (think of everything is a list); macros enables you to treat data and code as the same (no string parsing or other hacks are needed to modify lisp expressions). You combine these two features and you have a very clean way to modify code.

Edit: What I was trying to say is that Lisp is homoiconic, which means that the data structure for a lisp program is written in lisp itself.

So, you end up with a way of creating your own code generator on top of the language using the language itself with all its power (eg. in Java you have to hack your way with bytecode weaving, although some frameworks like AspectJ allows you to do this using a different approach, it's fundamentally a hack).

In practice, with macros you end up building your own mini-language on top of lisp, without the need to learn additional languages or tooling, and with using the full power of the language itself.

Miguel Ping
+1  A: 

In short, macros are transformations of code. They allow to introduce many new syntax constructs. E.g., consider LINQ in C#. In lisp, there are similar language extensions that are implemented by macros (e.g., built-in loop construct, iterate). Macros significantly decrease code duplication. Macros allow embedding «little languages» (e.g., where in c#/java one would use xml to configure, in lisp the same thing can be achieved with macros). Macros may hide difficulties of using libraries usage.

E.g., in lisp you can write

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

and this hides all the database stuff (transactions, proper connection closing, fetching data, etc.) whereas in C# this requires creating SqlConnections, SqlCommands, adding SqlParameters to SqlCommands, looping on SqlDataReaders, properly closing them.

dmitry_vk
A: 

Lisp macros represents a pattern that occurs in almost any sizeable programming project. Eventually in a large program you have a certain section of code where you realize it would be simpler and less error prone for you to write a program that outputs source code as text which you can then just paste in.

In Python objects have two methods __repr__ and __str__. __str__ is simply the human readable representation. __repr__ returns a representation that is valid Python code, which is to say, something that can be entered into the interpreter as valid Python. This way you can create little snippets of Python that generate valid code that can be pasted into your actually source.

In Lisp this whole process has been formalized by the macro system. Sure it enables you to create extensions to the syntax and do all sorts of fancy things, but it's actual usefulness is summed up by the above. Of course it helps that the Lisp macro system allows you to manipulate these "snippets" with the full power of the entire language.

dnolen