6 ms·
Because macros let you control code in a way libraries don't^1. Java now has this way to iterate over a collection of things: for(String exclamation: excl
by zck 11y ago
Because macros let you control code in a way libraries don't^1.
Java now has this way to iterate over a collection of things:
for(String exclamation: exclamations) {
System.out.println("I yell " + exclamation + " at you");
}
But this was only added in 2004!^2 So for almost 10 years, you had to manually iterate over stuff. How would you implement this as a library? Well, you could write a function that lets you write:
iterate_over_collection(exclamations, function(collection) {
System.out.println("I yell " + exclamation + " at you");
}
But this is much uglier than the prior code. Also, it might not act the same. For example:
int highest = ages[0];
for(int age: ages) {
highest = Math.max(highest, age);
}
Would this work in a lambda? Well, if the language you're using has real closures, yes -- but does it? Do you know offhand? With a macro, your code will work.
> Even if you ignore the above, there's probably a good reason why the language designers don't want to approve your language-level feature. ... it can also be that you -- the applications programmer -- simply aren't well-versed in language design and can't think past your particular use case :) This wouldn't mean the feature is worthless (after all, you need it!)
That seems like evidence for my point -- macros let you build the language up for your own use case, not anyone else's. Without macros, your choices are "either everyone can use it, or no one can use it". Macros let you have a choice of "well, I can use it, even if no one else wants it, if I find it useful."
Macros can be viewed as libraries that act on the language itself. There isn't a difference between language-level features and "library functions" in a language with macros.
[1] Without getting into a Turing Tarpit. We're talking about using things in easy ways, not what is technically possible but ugly and kludgy.
[2] It was released in Java 5.0: http://www.java-tips.org/java-se-tips/java.lang/the-enhanced-for-loop.html http://www.java-tips.org/java-se-tips/java.lang/the-enhanced...
- the_af 11y agoAgreed about your Java example. However, for the purpose of this discussion, let's assume we're talking about modern, well-designed languages without ugly kludges and with access to nice features such as lazy evaluation and real closures. > Macros can be viewed as libraries that act on the language itself. There isn't a difference between language-level features and "library functions" in a language with macros. I simply don't see why this is such a big deal. I need to see a real-world example (which, understandably, might be difficult to explain in a HN thread) of something that can be achieved with Lisp that is not reasonably achievable in elegant ways in other, non-Lisp modern languages. Again, let's assume we both understand the Turing Tarpit. By the way, I don't want to sound dense. I understand some features you only "get" when you use them. What little I've seen of Lisp (Racket, actually) seemed very interesting! It's just that I can't get that enlightened moment where I see why Lisp macros are that important in the real world. This is important to me because macros are one of the key features Lispers use to try to convince other programmers Lisp is awesome. And I can see they are interesting and useful; I just fail to see why they are a such big deal that they set Lisp apart and that, for example, Paul Graham would call Lisp his "secret sauce".
- charlieflowers 11y agoMany have said that the real benefit is that you wind up turning Lisp into the language that is perfect for your domain. When you start your project, you don't know enough to design the perfect language for your domain. You start coding in Lisp, and you begin to uncover patterns that express your domain. Eventually, you find your way towards building a small set of macros that beautifully, expressively capture your domain. Look for where Paul Graham talks about "bottom-up" programming versus "top-down" programming, and you'll find what he has to say about this. He says you do both in Lisp. Bottom-up is "changing the language to suit your problem." http://www.paulgraham.com/progbot.html http://www.paulgraham.com/progbot.html
- LanceH 11y agoOne that helped some Java friends understand is passing blocks of code, but still having it look like just writing code. Imagine instead of try/catch/finally, a transaction/commit/rollback in Java: transaction { // everything in here is in one transaction } commit { // do stuff if the commit is successful } rollback { // do stuff if we rollback } All the try's and catch's can be stuff into the macro. It can be made to nest transactions within transactions. For all practical purposes, you can't add that to Java. You'll always have to wrap up your transactions in boilerplate. The Lisp equivalent of what I want, the resulting code would look like: (transaction (do-stuff) (do-stuff-if-commit-successful) (do-stuff-if-rollback)) There are languages where I could define three blocks of code and pass them: transaction(stuff, commit-stuff, rollback-stuff) But that separates their definitions from their implementations. How could you write the Lisp transaction expression in another language so that it looks like it's part of the language? (serious question, CL is the only language I use capable of being that close) Maybe I could torture Ruby to come close, but it would be far more difficult than the macro I had to write for Lisp.
- blueblob 11y agoI find this argument pretty unconvincing too. >For all practical purposes, you can't add that to Java. You'll always have to wrap up your transactions in boilerplate. Aren't you wrapping the lisp code in boilerplate when doing the macro too? This appears to be the same as your other example. Java has lambda expressions (since Java 8) that could do this. If you are wrapping it in a macro, how is it different than wrapping it in a method? In Java 8, with lambda expressions, you can write code to wrap your transaction example to get something exactly equivalent to >transaction(stuff, commit-stuff, rollback-stuff) and you would be keeping the definition in the implementation. I also seem to be missing why it's important to be keeping the definition and implementation together.