Ask HN: Are macros still an enormous edge in the power of a language?
I use Lisp (Clojure) a lot and like it, but it seems like macros, except in specialized cases, aren't quite the game changer they used to be. They're much tougher to reason about than functions, can't be passed around as parameters, and can lead to "magic" DSLs that only their owners understand. Obviously they are sometimes very useful, and the built-in macros (e.g. cond, if-let) are essential, but I don't see them as such a game-changer in modern programming. I feel like over 90% of the edge Lisp has over Blub comes from its functional support, with less than 10% coming from macros. This makes the edge small enough that, in many circumstances, I can see languages like ML and Haskell winning; good static typing (e.g. ML, not Java) becomes a benefit on large projects.
I could be convinced that I am wrong and just don't "get" macros yet, but it seems like much of what's shown off in On Lisp et al is available nowadays in non-macro languages. I'd like to see modern uses for macros that aren't easy to implement in macroless languages.
54 comments
[ 0.23 ms ] story [ 42.9 ms ] threadhttp://ycombinator.com/arc/arc3.tar
If you want a single, self-contained example, here's one:
How do you implement that in a language without macros?Where object is the structure you're operating on, `selector` is a function taking an object and returning a pointer to the position [2]. The `action` is a pointer to an arbitrary function [1].
Your first example would look like this:
Or am I missing something you didn't include in the description?I was simply surprised that PG asked about something like that - I still think it's pretty simple and the code in C is as close to the macro as possible (with whatever object abstraction you need).
However I have to admit that I thought about other languages now and it seems that the described zap() is not possible in python... and that's interesting / worrying:
cannot assign a new value to `object[place]`, while has to do the lookup twice.You can do the same thing without macros. Granted, the compiler supports by generating code, so it is macro-esque.
In http://www.paulgraham.com/avg.html , PG argues that Lisp is more powerful than other languages because it has macros. However, in the 50 years since the invention/discovery of Lisp, is there now some language that has a new feature X that lisps don't have (and can't easily add)? Or are macros still the "pinnacle" of language features?
It's a bit more complicated than that. The key idea is that programs are composed of lists. But that idea is tied to several others:
http://www.paulgraham.com/diff.html
"Language X is more powerful than Lisp because it has feature(s) Y."
A has X? Well, implement that. It takes 2 days, but it will save you months later. B has Y? implement that. Now you have BOTH X and Y. A users will wish for Y, B users will wish for X. You? Both. Sorry, I meant "Lisper? Both".
http://www.newartisans.com/2009/03/hello-haskell-goodbye-lis...
The comments below the article also have some interesting and relevant things to say.
The rest of us just rewrite the critical stuff in whatever that's the most efficient, and the non-critical stuff in whatever that's easiest.
Oh screw this! Guys, I just converted from Common Lisp to CSS! The cascade is like OOP inheritance, without the annoying CLOS multiple-inheritance and over-configurable multiple dispatch. It has keyword methods, :hover, :active, etc, I can even implement list processing with getElementById! Take that, you Lisp weenies!
Macros are for introducing new control primitives, syntax and evaluation models into the language.
Except, I suppose, for perverse macros which expand into shorter code :)
First it finds all 'features' by traversing a 1-pixel wide representation of the text character's image. Then you describe which features you want to look for, in a macro language.
So (in layman's terms) , a 'c' is a top curve curving up, a lower curve curving down, and a stem at the left-hand side close to the left-hand of those curves. So 'c' would be about 3 rules, in this macro language.
It was fun but monstrously sluggish and I never got the 1-pixel wide algorithm (called skeletonisation) that I used, to work 100%.
One thing it did have going for it was you could identify really small font chars and even 10-pixel-high truetype fonts, as long as you normalise so each char is 60 pixels high.
I ported the system to C++ also in 2006 and had to write a simple Scheme-ish interpreter to turn the Lisp ruleset into C code.
Lisp macros operate on the abstract syntax of the code forms, after it's parsed. They're tree-transforms. They could also be nested, so one macro would operate on the result of another. All of them could also operate on the values read by reader macros, which are Lisp's C-like preprocessor macros: A lisp macro that frobs vectors could, for example, processes code that looks like any of the following: foo[], vector foo, (vector foo), (array 1 ..), etc. if the appropriate reader syntax is defined for them. That's right, ONE macro to do all that.
If you want to explore the highest level of code transformation, grab a nice little book called "Term-Rewriting and all That".
P.S. It's actually not a nice book. It's a theoretical mindfuck that will have you chasing abstract algebra down rabbit holes. It took a good year of my life and I still have no clue. </confession>
Example, in pseudo code:
<code> defun regex_find(some_string, regex) return regex.compile().find(some_string)
def compilermacro regex_find(some_string, regex) if IS_STRING_LITERAL(some_string) and IS_STRING_LITERAL(regex) SUBSTITUTE exec(regex.compile().find(some_string)) elif IS_STRING_LITERAL(regex) SUBSTITUTE exec(regex.compile()).find(some_string) else DO_NOTHING return
</code>
regex_find(MYSTRING, '\w(.+)\b')
A language without compiler macros would compile the regex at runtime, even in those cases that it is a string literal, and could be compiled at compile time. And in the case that both the string and the regex are string literals, the result itself could be computed at compile time. Another alternative, used by lots of languages/libraries, is to maintain a cache of recently compiled regular expressions. Of course, this doesn't help when you have a loop that uses 15 regular expressions but it only caches the last 14, for example.
This is one reason why CL-PPCRE is faster than the C PCRE in benchmarks, it makes great use of compiler macros.
This is a very concrete real world thing, that isn't endlessly debatable, unlike regular macros, and it could be included in languages without any special macro syntax.
One example of a time I really wanted compiler macros in Python was when I was doing some parsing of binary files, using the struct module. The struct module lets you do stuff like struct.decode(data, 'UUUIUH'), where the UUUIUH is some code for Unsigned long, unsigned long, 32bit int, whatever. However, it parses the little code each time you want to use it, which ends up taking way longer than the actual decoding itself. In python 2.x, whatever I was using, they didn't have any way to make any sort of compiled decoder, you had to just use it as above, which is silly, since the code is almost always going to be a string literal and 99% of the work could be done once when the code is compiled.
Nitpick: it depends on the replacement policy whether it helps. If the cache is MRU, for example, it would work fine.
If you have 14 slots in your cache, and you loop over 15 regular expressions, no cache policy (unless it also tracks regexes it has already lost from the cache, which effectively gives it more than 14 slots) will work, except maybe some kind of cache that just remembers the first 14 regexes it sees, and only forgets one randomly every 10 more regexes it sees. Such a cache policy would be really really stupid in most cases not specifically designed to thwomp, say, an LRU cache or a regular deque or something.
http://common-lisp.net/project/iterate/doc/index.html
http://www.lispworks.com/documentation/HyperSpec/Body/06_a.h...
In a language without macros, I'd be stuck with loop and curse every time I need to write some non-trivial iteration code. But thanks to macros, I can use this wonderful iteration facility as a library, and have great integration with CL, just like if it was part of the language.
Problem: I always hated that (loop for element in list collect (my-fun element)) translates trivially to (mapcar #'my-fun list) but (loop for element in list collect (my-fun element my-constant)) doesn't. You'd either have to stick with the loop version or use something ugly and inefficient like (mapcar #'my-fun list (make-list (length list) :initial-element my-constant)) or write out the lambda: (mapcar (lambda (element) (my-fun element my-constant)) list) (my-constant could actually be an arbitrary form).
None of these solutions appealed to me. So I wrote fmask. With it, the example is simply rewritten as (mapcar (fmask #'my-fun ? (? my-constant)) list)
Another example:
=>((3 3) (8 9) (2 3))Here's the implementation:
http://paste.lisp.org/new
fmask's implementation with syntax coloring:
http://paste.lisp.org/display/81488
I also have a more extended example usage in an annotation at the bottom.
(mapcar (rcurry #'parse-integer :junk-allowed t) '(" 1" "2xx" "3" "foo")) (1 2 3 NIL)
... whereas your more complex example doesn't strike me as an improvement over lambda + backquote:
(mapcar (fmask #'list ? (? (1+ (length ?)))))) vs.
(mapcar (lambda (n list) `(,n (1+ (length ,list)))) numbers lists)
... which might be longer than the fmask version but has the advantage of being immediately readable to any Lisp programmer, and of allowing arguments to be reused, or used in a different order than they appear.
Doug Hoyte has a nice read macro called #`:
(mapcar #2`(,a1 (1+ (length ,a2))) numbers lists)
Here's another example. I work on a spreadsheet app. A spreadsheet has many operations that apply to either a row or a column (e.g. selecting a row/col or resizing it). The fact that they occur in transposed dimensions make them difficult to abstract over with functions. You probably end up with a bunch of boilerplate that says things like "if this is a column, change the first co-ordinate, otherwise change the second", or most likely would just write the code twice, once for the horizontal case and once for the vertical. In Lisp, we can write such a function for one dimension and then use macros to generate a transposed version of the same function that does exactly the same thing in the other dimension.
Do you need macros to make such a program work? No. Can you use them to make the program shorter, clearer, and more generalizable? Yes.
http://gmarceau.qc.ca/blog/2009/05/speed-size-and-dependabil...
Lisp and scheme do pretty poorly on the power scale. Even going back to the original data neither finish in the top 10 and Lisp gets beaten by java.
The entire point of programming is automation. The question that immediately comes to mind after you learn this fact is - why not program a computer to program itself? Macros are a simple mechanism for generating code, in other words, automating programming. Unless your system includes a better mechanism for automating programming (so far, I have not seen any such mechanisms), _not_ having macros means that you basically don't understand _why_ you are writing code.
This is why it is not surprising that most software sucks - a lot of programmers only have a very shallow understanding of why they are programming. Even many hackers just hack because it's fun. So is masturbation.
This is also the reason why functional programming languages ignore macros. The people behind them are not interested in programming automation. Wadler created ML to help automate proofs. The Haskell gang is primarily interested in advancing applied type theory.
Which brings me to my last point: as you probably know, the reputation of the functional programming people as intelligent is not baseless. You don't need macros if you know what you are doing (your domain), and your system is already targeted at your domain. Adding macros to ML will have no impact on its usefulness for building theorem provers. You can't make APL or Matlab better languages for working with arrays by adding macros. But as soon as you need to express new domain concepts in a language that does not natively support them, macros become essential to maintaining good, concise code. This IMO is the largest missing piece in most projects based around domain-driven design.
Imagine this Christmas, Perl 6 is out with some killing features. Now, your language (Python? Haskell? ML? Whatever) does not have these features. What would you do?
You have 2 choices, basically: a. forget about that features, write a long blog in which you prove how your language can do just the same thing, albeit "a little" less elegant b. Switch to perl. This is not always possible, btw, since you may sacrifice some OTHER features that your language has.
How about a LISPer? Well, simple, let's implement these features. Keep in mind: All languages are Turing equivalent, meaning they can do roughly the same thing. There are two differences: 1. some do some task more elegantly (aka easier to maintain) than others. 2. Some have better support (eg. corporates, community, etc.) than others
Thus, you CAN always write the exact same features in LISP. Now, the definition of the macros WILL be messy. However, as a good practice, you spend 2 days debug that holy mess, and jam it into a file you banish somewhere with a dot precede the directory. Next time, you can use the features, pretending that you are using Perl 6!
This is my strategy. My Scheme code always has for-loop (purely functional, too), foreach-like, OO-like features. Of course, Scheme (remember, it's not even CL) does not provide these, but I can either implement them or find the implement somewhere. I have once even try to implement Icon-like mechanism with Macro + Continuation (okay, the Scheme implementation that time was a bit fuzzy, so I failed. But, with pure Scheme, it SHOULD be possible). That's the main power: you can copy others' ideas extremely fast! Without throwing away your old code, too. Hurray to Macro!