Macros: what are they good for?
Paul Graham wrote an entire book about the uses of Common Lisp macros. In it, he lists the basic things you can do with macros (pp 107-109). Here's my condensed version:
A lot of this has to do with strict vs lazy functions, that is, strict functions, which evaluate all their arguments exactly once, at the time when they are called, vs lazy functions, which only evaluate an argument at the time(s) (if any) the value is required. Various languages allow this, e.g., Scala, which is strict by default but allows lazy functions, or Haskell, which is lazy by default but allows strict behaviour. However, many programmers use languages that are completely strict (C, C++, Java, etc.) or restrict themselves to subsets of a language which are entirely strict (Lisp without macros, Perl and Python without certain libraries).
On Lisp also lists some good and bad points about macros (again, my condensed version):
The Good:
What you can ignore with Haskell:
2. not a problem; lazy evaluation by default
3. lazy evaluation by default
5. lexical scope by default
7. compiler does it automatically
So in Haskell, all you need to worry about are 4 & 6 (which are the same thing), and the big 1, which is hard to equal without Lisp's syntax tree data structures. I haven't learned enough Haskell to be sure, but some things I've read indicate Haskell allows these things too, in a nice way.
- Transformation
- treat argument as a syntax tree data structure rather than code to be evaluated
- argument can be transformed before being compiled (example)
- treat argument as a syntax tree data structure rather than code to be evaluated
- Binding
- if you want to pass an argument to a function that doesn't evaluate its argument, you can't evaluate your argument either
- if you want to pass an argument to a function that doesn't evaluate its argument, you can't evaluate your argument either
- Conditional evaluation
- don't evaluate any argument whose value isn't needed
- don't evaluate any argument whose value isn't needed
- Multiple evaluation
- do as many evaluations of an argument as necessary
- do as many evaluations of an argument as necessary
- Using the calling environment
- equivalent to referring to global variables
- so much for lexical scope
- this is almost always a mistake (you should pass it in as a parameter instead)
- equivalent to referring to global variables
- Wrapping a new environment
- macros can redefine variables that appear in their arguments before the argument is evaluated
- this is useful, but creates nasty bugs when it happens by accident
- macros can redefine variables that appear in their arguments before the argument is evaluated
- Saving function calls
- Do as much work at compile time as possible
- Similar to inline functions, constant folding
- Do as much work at compile time as possible
A lot of this has to do with strict vs lazy functions, that is, strict functions, which evaluate all their arguments exactly once, at the time when they are called, vs lazy functions, which only evaluate an argument at the time(s) (if any) the value is required. Various languages allow this, e.g., Scala, which is strict by default but allows lazy functions, or Haskell, which is lazy by default but allows strict behaviour. However, many programmers use languages that are completely strict (C, C++, Java, etc.) or restrict themselves to subsets of a language which are entirely strict (Lisp without macros, Perl and Python without certain libraries).
On Lisp also lists some good and bad points about macros (again, my condensed version):
The Good:
- Computation at compile time (as much as possible)
- a well-made compiler should be doing this automatically
- Integration (e.g., write a compiler that translates a mini-language into the base language)
- This is basically another way of saying "compute at compile time"
- Saving function calls
- again, a well-made compiler should be doing this automatically
- Macros aren't first class
- they should be, just like functions
- Lack of clarity
- code that rewrites code can be disconcerting at first
- Lack of debugger support
- debuggers are typically tuned to help you debug stuff that happens at run-time, not compile-time
- Macro recursion
- is even more confusing than normal recursion, but you managed that, right?
What you can ignore with Haskell:
2. not a problem; lazy evaluation by default
3. lazy evaluation by default
5. lexical scope by default
7. compiler does it automatically
So in Haskell, all you need to worry about are 4 & 6 (which are the same thing), and the big 1, which is hard to equal without Lisp's syntax tree data structures. I haven't learned enough Haskell to be sure, but some things I've read indicate Haskell allows these things too, in a nice way.
0 Comments:
Post a Comment
<< Home