Live data from Hacker News

Why Lisp?

blog.rongarret.info

231–240 of 248 posts

Re: Why Lisp?

#231
post #46

Earlier quoted context omitted.

>Aren't macros breaking the homocionicity of the lisps? No. How would macros break homoiconicity? They expand into atoms and lists (and other datatypes), the same stuff of which macro-free programs are made. >And making maintenance more difficult? No. Unless you intentionally write unmaintainable macros. It's the same as if you write unmaintainable functions, classes, etc. They're just abstracting a different thing—…

I have heard the “first rule of the macro club” to be “don’t write macros”. The idea is before writing a macro, you should try writing it as a function instead. If that is possible, that is usually better, because functions, unlike macros, can be passed around as first-class values, and I think they are easier to debug. You should break that rule only when the behavior can’t be written as a function, such as these ca…

>You have profiled the program and determined that it is better to run the function at compile-time. For example, you might want to make `(regex "[a-z][a-z0-9]+")` compile the string to a regular expression at compile-time instead of run-time

I think it would be more appropriate to use a compiler macro here.

Re: Why Lisp?

#232
post #161

Earlier quoted context omitted.

One 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 t…

I 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 wrap…

@ zck:

> What are the odds that Java 8 has provided everything you could want in out of Java? Macros let you add things in a better way than you could otherwise get. Look back to my prior example of Java 5's expanded for.

Again, please understand we non-Lispers find this argument utterly unconvincing :) Java in particular is a terrible example: it's a language that for many years remained in the dark ages, and now it's finally getting modern features retrofitted into it, while at the same time attempting to keep some sort of backwards compatibility, and the whole process is very painful.

Let's all agree to stop talking about Java. Let's assume we all agree working in a language that until very recently didn't have lambdas is painful. And that requires a horrific amount of boilerplate.

Let me re-throw your question back at you: what exactly can Lisp do, which has practical implications, that a modern language with lambdas, closures and lazy evaluation cannot accomplish in elegant ways? If you mention Java again, you lose :P

Re: Why Lisp?

#233
post #226

The article doesn't discuss macros, which is one of the answers to "Why Lisp?" I didn't "get" macros until I read a footnote in the (freely available) book Practical Common Lisp . In chapter 7, it introduces the `dolist` macro. DOLIST loops across the items of a list, executing the loop body with a variable holding the successive items of the list. This is the basic skeleton (leaving out some of the more esoteric opt…

Similar, similar, only if you don't look too closely: what about break and continue? They are usually supported by native foreach construct but are difficult to build with macros..

    only if you don't look too closely
Look as closely as you'd like. :)

    what about break and continue?  They are usually
    supported by native foreach construct but are
    difficult to build with macros..
For break, DOLIST uses return. I haven't worked with Lisp in some time, so I don't remember if it has a dedicated continue. But a continue is just a break in a loop that doesn't loop. Or a GOTO by another name. Or a jump to a particular case in a switch.

But if your looping macro uses a native looping construct or another macro that supports break and continue, then your looping macro will inherit that support, provided that you're careful when writing the macro to do nothing that will break it.

If your looping macro doesn't use a construct that supports break and continue, then you're still in luck. Continue I described above. Like continue, break is also GOTO by another name. You've got TAGBODY, GO, CALL/CC, etc.

Why do you think that supporting break and continue in macros is difficult?

Re: Why Lisp?

#234
post #232

Earlier quoted context omitted.

I 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 wrap…

@ zck: > What are the odds that Java 8 has provided everything you could want in out of Java? Macros let you add things in a better way than you could otherwise get. Look back to my prior example of Java 5's expanded for. Again, please understand we non-Lispers find this argument utterly unconvincing :) Java in particular is a terrible example: it's a language that for many years remained in the dark ages, and now it…

You seem to be concerned by the use of "Java". It's a fine example, and one I think I've explained why very well. It also seems petulant to declare Java off-limits.

But let me try again. For _any given language_, there are things you may want that the language doesn't provide. Macros let you do that in an elegant way. The example I gave above -- based off LanceH's -- of transactions is a way where the macro-based solution is more elegant than non-macro solutions.

Here's another example. Arc, like any language, has a built-in way of setting variables. It's called `assign`, and can be used as follows:

    arc> (assign a 3)
    3
    arc> a
    3
But no one uses it. Instead, people use =. = is a macro that's provided with the language. Because it's a macro, that means that if it wasn't provided, you could write it yourself.^1

What's the benefit of = ? It lets you set values of more than just variables:

    arc> (= my-table (table))
    #hash()
    arc> (my-table 'key) ;;look up the value
    nil
    arc> (= (my-table 'key) 'value)
    value
    arc> (my-table 'key)
    value
Note that in the second prompt, we're attempting to look up the value of 'key in the hashtable my-table, and we see that there isn't one there. We then set it in the third prompt, and look it up again in the final.

This works on the concept of "places". The "place" we try to set in `(= (my-table 'key) 'value)` is the association of 'key inside my-table.

Why is this beneficial? If you know how to get a value out of a data structure, you can now set it. This code is extremely clean and understandable compared to a non-macro version. It exhibits the principle of least surprise, and it's obvious how to set other data structures in an elegant way.

You can't do this without macros.

[1] If your objection here is "but it comes with the language", you're missing the point.

Re: Why Lisp?

#235
post #234
post #232

Earlier quoted context omitted.

@ zck: > What are the odds that Java 8 has provided everything you could want in out of Java? Macros let you add things in a better way than you could otherwise get. Look back to my prior example of Java 5's expanded for. Again, please understand we non-Lispers find this argument utterly unconvincing :) Java in particular is a terrible example: it's a language that for many years remained in the dark ages, and now it…

You seem to be concerned by the use of "Java". It's a fine example, and one I think I've explained why very well. It also seems petulant to declare Java off-limits. But let me try again. For _any given language_, there are things you may want that the language doesn't provide. Macros let you do that in an elegant way. The example I gave above -- based off LanceH's -- of transactions is a way where the macro-based sol…

I disagree your example of Java is fine. It's not petulant to declare it off-limits, just as it's not petulant to declare COBOL off-limits. We are discussing features and extensibility of finer languages than Java.

> The example I gave above -- based off LanceH's -- of transactions is a way where the macro-based solution is more elegant than non-macro solutions.

Here we disagree. In your example of transactions, you didn't shown that macro-based solutions are more elegant than non-macro based solutions; you merely showed that Java before version 8 wasn't very good (and when someone replied "but we can do better with Java 8!" you basically replied "ok, but how do you know Java 8 is enough for some other unspecified problem?"). Your answer is unconvincing, especially since non-macro-based solutions to your example in other languages, such as Scala, are equally elegant to Lisp's, because Scala has support for first-class functions and closures. Let me preempt a "but how do you know that's enough for Scala?"... I don't know. Show me why it's not enough!

Re: your second example with assign and the = macro. I admit I don't understand it yet; I'll have to think some more about it.

edit: let me go back to this assertion:

> For _any given language_, there are things you may want that the language doesn't provide. Macros let you do that in an elegant way.

I find this problematic, for two reasons. First, we've acknowledged macros cannot solve everything; after all, there are new releases and multiple implementations of Lisp languages. What someone else said: "if it's not in the 'substrate', macros can't do it". Second, that macros let you do some (admittedly cool!) things doesn't automatically show that these same things cannot be accomplished in reasonably elegant ways in other languages. One thing doesn't imply the other!

Re: Why Lisp?

#236
post #235
post #234

Earlier quoted context omitted.

You seem to be concerned by the use of "Java". It's a fine example, and one I think I've explained why very well. It also seems petulant to declare Java off-limits. But let me try again. For _any given language_, there are things you may want that the language doesn't provide. Macros let you do that in an elegant way. The example I gave above -- based off LanceH's -- of transactions is a way where the macro-based sol…

I disagree your example of Java is fine. It's not petulant to declare it off-limits, just as it's not petulant to declare COBOL off-limits. We are discussing features and extensibility of finer languages than Java. > The example I gave above -- based off LanceH's -- of transactions is a way where the macro-based solution is more elegant than non-macro solutions. Here we disagree. In your example of transactions, you…

>In your example of transactions, you didn't shown that macro-based solutions are more elegant than non-macro based solutions; you merely showed that Java before version 8 wasn't very good...

I think it wasn't clear what I was referring to. I was talking about not Java, but a Lisp-style solution. Here it is with macros:

    (transaction (do-stuff)
                 (do-stuff-if-commit-successful)
                 (do-stuff-if-rollback))
Here's the version without macros:

    (transaction (lambda () (do-stuff))
                 (lambda () (do-stuff-if-commit-successful))
                 (lambda () (do-stuff-if-rollback)))
The macro-based solution -- which doesn't mention Java -- is more elegant. You don't need the lambdas if you use macros (why? Well, you don't always want to rollback, right?)

>...when someone replied "but we can do better with Java 8!" you basically replied "ok, but how do you know Java 8 is enough for some other unspecified problem?"

That's the point -- a language with macros is extensible in a way that languages without macros aren't. So unless you believe that your language happens to be perfect, having macros would make the language more powerful.

>Re: your second example with assign and the = macro. I admit I don't understand it yet; I'll have to think some more about it.

Feel free to contact me if there's anything else I can explain -- I'm probably going to forget to check this thread soon.

>...we've acknowledged macros cannot solve everything; after all, there are new releases and multiple implementations of Lisp languages.

I'm not sure why new Lisp releases show that macros are not useful. You could write anything in assembly, but other languages are still released. And design is important -- which sets of functions, and macros should be provided with a language? Does having a release of Scala that includes functions mean that there's no need for user-defined functions?

>Second, that macros let you do some (admittedly cool!) things doesn't automatically show that these same things cannot be accomplished in reasonably elegant ways in other languages. One thing doesn't imply the other!

It doesn't mean that, no. However, I don't see elegant ways to do this kind of thing in other ways. If you can show me some, I'd be interested.

Re: Why Lisp?

#237
post #215

(I have a subtle optimization for S-expression syntax)(I am surprised nobody ever thought of it)(When S-expressions are in a sequence use an extra (special) delimiter plus the regular token separator to separate expressions)(Maybe use dot? (period I think some call it)) Like so. I think it could catch on. And you get rid of so many round bracket block delimiters (at least for S-expressions on the same level. for nest…

You'll love my binding-block macro then. You can find it here: https://github.com/rongarret/ergolib

Interesting. Now all I have to do is learn a bit of Common Lisp. I'm surprised my half-jokey question turned out to have a legitimate response :)

Re: Why Lisp?

#238

(I have a subtle optimization for S-expression syntax)(I am surprised nobody ever thought of it)(When S-expressions are in a sequence use an extra (special) delimiter plus the regular token separator to separate expressions)(Maybe use dot? (period I think some call it)) Like so. I think it could catch on. And you get rid of so many round bracket block delimiters (at least for S-expressions on the same level. for nest…

So this code (from https://github.com/axch/test-manager/blob/c4fc3224e873716c58... ) (define-record-type omap-entry (make-omap-entry key item next prev) omap-entry? (key omap-entry-key set-omap-entry-key!) (item omap-entry-item set-omap-entry-item!) (next omap-entry-next set-omap-entry-next!) (prev omap-entry-prev set-omap-entry-prev!)) would be written like this? (define-record-type omap-entry make-omap-entry key it…

Sweet-expressions is a cool idea. Was it Python that started the whitespace-is-significant trend? I'm wondering why someone doesn't create a Scheme or a Lisp with this as valid syntax out of the box. I think you'd still need to support regular S-expressions though, right?

Re: Why Lisp?

#239
post #183

Earlier quoted context omitted.

>maintenance If used right, macros make things much more maintainable. If you have a hundred nearly-identical codeblocks, where only (say) a string constant is varying, and suddenly you need to change what those blocks do, bam, you've got a hundred blocks to change. If those blocks had been refactored with a macro, all you have to do is change the macro once. It also prevents the new guy from coming and doing a banda…

Not a lisper, so bear with me please. Couldn't you do this with a function?

The code block in question might be something that doesn't really deserve a whole function on its own, and could also involve local control-flow statements (return/continue/break/etc.) that can't really be outsourced to a function nicely. For example, you're parsing a line from a flat file and you want to assign a value to a certain variable based on the first word on the line. So you have a few dozen/a hundred lines of

if ( key == "hitpoints" ) {player.hitpoints = value; return;}

if ( key == "mana" ) {player.mana = value; return;}

Those "return"s make it rather awkward to use a function. At best, said function would have to return a bool and you'd end up with something like

if ( maybe_assign( key, "hitpoints", &player.mana ) ) return;

which hardly gains you anything and in fact reduces readability a fair amount.

Re: Why Lisp?

#240
post #236
post #235

Earlier quoted context omitted.

I disagree your example of Java is fine. It's not petulant to declare it off-limits, just as it's not petulant to declare COBOL off-limits. We are discussing features and extensibility of finer languages than Java. > The example I gave above -- based off LanceH's -- of transactions is a way where the macro-based solution is more elegant than non-macro solutions. Here we disagree. In your example of transactions, you…

>In your example of transactions, you didn't shown that macro-based solutions are more elegant than non-macro based solutions; you merely showed that Java before version 8 wasn't very good... I think it wasn't clear what I was referring to. I was talking about not Java, but a Lisp-style solution. Here it is with macros: (transaction (do-stuff) (do-stuff-if-commit-successful) (do-stuff-if-rollback)) Here's the version…

The problem that I have with lisp macros is that the elegance you gain at the syntax level is effectively a tradeoff with pragmatism when other people read and use the code. Java was developed with parts of C++/C as inspiration and parts of that language were left out. Particularly, operator overloading was left out (which can be viewed as a very restricted example of modifying the language), presumably because it's not an immediate thought when viewing the code that the operator isn't doing what you expect. While it's undeniable that macros make the syntax nice to look at, the same argument that lisp becomes a new language as you write your program means that every separate codebase has a lot more reading to understand because you have to go through all the macros. New releases in most languages introduce new features (and standardize functions, fix bugs, etc), as far as I can see, new releases in lisp enforce a standard (common) base set to decrease the amount of work required in learning new codebases.
Post reply on HN