I'm glad to see this, and even more interested that it was written in 2005. I've read lots of rants about how awesome Lisp is supposed to be from Paul Graham and Steve Yegge and all. But I never see anything significant being written in it or using it, so I haven't felt all that tempted to try learning it.
Meanwhile, I've been using Ruby for a while, and the metaprogramming capabilities are pretty cool. It sounds a lot like what all of the Lisp enthusiasts are saying is so awesome about Lisp to me. So I've always wondered what it is that's so awesome about Lisp that Ruby doesn't already have.
I couldn't have written a unit test framework which evaluates at compile-time in Ruby, but I did in Gambit Scheme. (If any test fails, the compiler exits in error, and no library or executable is produced.)
I don't want my teammates writing macros. When the whole programming community constantly talks about situations within regular, more expressive languages (with syntax) like Python et al, where teammates look at each others code and are baffled ... I struggle to see a scenario where inventing syntax through macros is a good idea, and won't make that situation so much worse.
> arbitrary syntax transformation in macros
See above.
> object system as powerful as CLOS
If I wanted object systems, there are plenty of options that don't involve so many parens, and do involve far more expressive syntax than lisps offer.
> and the ability to comfortably use almost any paradigm
I kinda want my team to pick a set of known paradigms. This is not an advantage. I've never used a modern programming language and thought 'this lacks paradigms, if only I could let my team invent some, that none of us will understand 6 months from now'.
> It's not always useful, but you'll be thankful when it is.
From what you've said, I won't. I'll just be upset my teammates invented their own language with macros and whatever paradigms they chose to use that day.
The lisp 'trade-off' (homoiconicity over expressive syntax) just seems like it's looking for power in the wrong places. Power like that is more often bad than good.
On large team Lisp projects, you don't have every individual person arbitrarily invent their own idiosyncratic abstractions. The project as a whole has a common set of abstractions that fit the domain, which are designed and modified the way you would design the abstractions regardless of the language and programming paradigm you're using. Macros are a language tool that lets you build abstractions, in some cases imo allowing better abstractions than would be possible without macros, most often ones simulated somewhat clunkily with OO features (or something godawful like C++ template metaprogramming). But if you have a large team project you still need an actually functioning team, common abstractions, project management, documentation, code review, the usual stuff. Successful large Lisp projects of course do all that.
I don't see a meaningful difference here between Lisp and, say, C++. If you have a dysfunctional team writing C++, full of cowboy programmers who invent their own idiosyncratic abstractions without coordinating with anyone, the fact that C++ doesn't have proper macros doesn't really meaningfully limit the resulting damage. What stops it from happening in functional teams isn't that C++ the language prevents it from happening, but that the team has some kind of working process that allows them to successfully write code as a team.
> Macros are a language tool that lets you build abstractions, in some cases imo allowing better abstractions than would be possible without macros
I guess 'better' is the operative word I question here. You're creating syntax that no-one has seen before. How is that a good thing? Meanwhile in languages like Python, with syntax built-in, people build large apps without ever doing this, making their code far more understandable for the next person that touches it.
> I don't see a meaningful difference here between Lisp and, say, C++.
Agree :) (joke)
> If you have a dysfunctional team writing C++, full of cowboy programmers
At no point am I suggesting we can save ourselves from cowboys ...
> What stops it from happening in functional teams isn't that [...] the language prevents it from happening, but that the team has some kind of working process that allows them to successfully write code as a team.
But people are arguing 'the whole point' of lisps is that you can be free to do this, and that this justifies the 'paren-crazy', syntax-less language trade-off, and I'm yet to see any justification it's necessary to allow this at all.
What I'm saying is that all the lispy parens give you are footguns and all you're saying is that good teams don't use those guns. Well then why give them the guns at all. Why not give them a language with decent syntax instead.
> in languages like Python, with syntax built-in, people build large apps without ever doing this, making their code far more understandable for the next person that touches it.
No. Lack of macros makes code bigger, more error prone and harder to understand.
When I use something like cl-ppcre:do-register-groups, this one word hides all the loop orginization, and underlines meaning of local variables which are bound to regex groups.
Or cl-sql:select, which allows programmer to use SQL syntax as part of lisp while hiding special chars escaping, converting numbers to strings, ensuring sql-syntax is correct.
Or simplier example: gl:with-primitives, which hides glBegin(...); ... glEnd(). Note, it not just hides, it makes impossible to forget glEnd.
Macros makes life easier. Otherwise no one would use it. Macros such a convinient way to make an abstractions, that switching from Lisp to any other language becomes a great pain. One start to see tons of boilerplate code, which needs to be written by hands, checked for errors with eyes, which hides logic somewhere inside of boilerplate... It becomes better with practice, but nevertheless lisp nostalgia will last forever.
> No. Lack of macros makes code bigger, more error prone and harder to understand.
I mean, that's provably not true. Or at least not provably true. Let's me throw out at alternative statement: Macros make code harder to understand.
> When I use something like cl-ppcre:do-register-groups, this one word hides all the loop orginization
You don't need a lisp to hide things.
> Or cl-sql:select, which allows programmer to use SQL syntax as part of lisp
Eugh, why would I want SQL interspersed with my lisp.
> [...] gl:with-primitives, which hides glBegin(...); [...] Note, it not just hides, it makes impossible to forget glEnd.
Python has 'with' statements and 'context managers' that do this (make sure some cleanup happens). [1]
> Macros makes life easier [...] such a convinient way to make an abstractions
They might make life easier for the person writing them in the short term. And I can see how when you find a really solid case for a macro and document it well, open source it etc, it can be broadly useful, but then why not add that case to the damn language. Instead you open the floodgates to everyone in your team writing macros that aren't widely understood.
> Otherwise no one would use it.
A lot of people avoid them like the plague in all other languages.
> but nevertheless lisp nostalgia will last forever.
So python's got a with statement. What of it didn't? Well then, you're screwed. Hope you like calling file.close().
If lisp didn't have a with statement, you could add one yourself. And if scheme didn't have a with statement... well, you could always add one, but before dynamic-wind got added, it could break if you weren't careful. Some things you can't just patch in. Anyways, now you'll say, "but python does have a with statement!" But that was just an example: there are things python will never have that you can add to lisp yourself. Like multimethod dispatch and generics, anaphoric operators, lazy module loading, pattern matching, ADTs, regex literals (with readtables), non-deterministic operators (the classic amb/assert operators, for concisely expressing searching a list (although they're admittedly more toys than anything else)), and a full-on looping construct that allows for awk-style record processing of arbitrarily typed records from arbitrary sources (field splitting is handled by other functions). And that's just a few examples I came across. There are dozens of others.
That would never all be added to the language: there's way too much, and it's too specialized.
As for your theory that macros make code harder to understand, you don't have any proof. In fact, you have negative proof, as there's plenty of logic, good programming practice, and individual cases indicating that careful use of macros can make code easier to understand.
> But that was just an example: there are things python will never have that you can add to lisp yourself.
I don't want to. I want a language that has sensible norms.
> As for your theory that macros make code harder to understand, you don't have any proof. In fact, you have negative proof,
Do I?
> as there's plenty of logic,
What logic? Show me. I simply don't believe you, as it's not provable.
> good programming practice
Still not proof. I believe the opposite, that it's not good programming practice to use macros.
> and individual cases indicating that careful use of macros can make code easier to understand.
So anecdotal evidence, not proof.
You have no more proof than I have. My personal experience is that in most mainstream languages, the most common arguments are over invention of frameworks/libs vs using existing ones, and those devs certainly would not be happy having macros thrown in the mix.
Lisp has sensible norms: Unlike you, however, we acknowledge that what may be a sensible norm for one situation isn't for another.
As for it not being good programming practice to use macros, it isn't good practice to use them excessively. However, they can be used to make your code much clearer. Here's the reasoning (no, it's not proof, you're right on that front, but it's better than what you've got, which is just your opinion, AFAICT):
The larger a codebase grows, the more likely it becomes to contain bugs. The most common way to decrease the size of the codebase is to abstract away common idioms, only express them in one place, and reference that from other parts of your program. This is what functions and objects do. However, some idioms are incapable of being expressed as functions or objects. These idioms must be repeated over and over again, and often result in insidious bugs when somebody gets them subtly wrong. Macros allow you to capture those idioms and express them once, eliminating bugs. They also make the code easier to understand, by abstracting away complex logic.
So, if you really think macros are bad, than what's wrong with that line of thinking?
Again, in your opinion. And sorry to start blunt because I'm trying to round this out as we're getting nowhere :)
To me, writing my code like it's an AST, amid a ton of parens is not sensible. I've happily not done so in many other languages for over a decade, and I'm very happy with that state of affairs (or at least I'm relatively happy with the modern incarnations of C-style langs, e.g. Rust, Crystal, ES6, and I would be very unhappy if I had to chuck that in tomorrow and write lisp).
Generally agree with your train of thought on abstractions, and see how you arrive at macros. But my take is that it's simply not a good idea to go as far as macros, especially when the syntax sacrifice is so great. If the language is truly missing something, take it up with the language authors.
(I've never encountered a situation in > 6 years of writing Python where I encountered something I could not do and needed macros for).
Again, my basic opinion is I see it as a trade-off, and one that I don't agree with. I think many others feel the same way if they get as far as understanding the purported benefits of homoiconicity. But most aren't willing to argue it on Hacker News and get downvoted to high hell (which is why I used a throwaway).
(I should note meanwhile in another thread someone wrote off the entire JS community as 'full of self-righteous assholes' [0] and got upvoted to top post).
Thanks for the discussion, it was an interesting experiment in arguing these thoughts I've had for a long time, as an 'abandoned' Clojure-curious dev. I'm a bit sad I didn't get any new answers, but appreciate the insight all the same.
No, what he's saying is that a good team knows when to use the guns, and how to aim them. A good OO team knows how to layer their types, how to relate them, and when to stop. A good asm team knows how to handle nonlocal jumps, when to use them and when not to. Every paradigm has its guns. You're just paying extra attention to the lisp ones.
> You're just paying extra attention to the lisp ones.
Because people in this thread are attempting to put-forward lisps' macros (and writing code as an AST) as some big win, without acknowledging that it comes with a massive cost. Lisp-y, inexpressive, paren-soup syntax. I'm saying myself (and presumably many others else lisps would be more popular) don't buy the trade-off.
If you like it, and your team like it then great. But don't act like it's definitely win-win, and people who choose not to use lisps just haven't achieved enlightenment yet.
I never said you haven't acheived enlightenment...
But Lisp's syntax is actually pretty expressive and nice, once you get used to it, IMHO. You continually deny this, and while you're free to disagree (many do), you continually argue that the mine and other's opinions are Bad and Wrong. I find this frustrating.
This feels like it's fallen back into a more boring criticism: you just don't like Lisp's syntax. Ok, fine, everyone has a criticism about every other programming language's syntax. Maybe I hate Python's mandatory indentation. These kinds of criticisms are not really the most interesting ones though.
I don't think you can put those things on the same scale. I think it's telling that you dismiss Lisp's syntax like it's unimportant rather than acknowledging that it's the several hundred lb elephant in the room whenever it comes to talking about lisps.
What I'm arguing is that Lispers say that all the parens and lack of other, more expressive constructs are worth it because you get macros. I'm arguing that's a bad trade-off, because 1. that's a big sacrifice, 2. macros aren't a win.
GPP asked what lisp's got that Ruby doesn't. I told him. Lisp isn't the end-all be-all. If you can't trust your teammates, than maybe you shouldn't be using Lisp. But keep in mind that you can write INTERCAL in any language. Lisp just makes it easier, because Lisp does its best not to limit you.
Besides, who would invent their own language in macros for no reason?
Also, the syntax is one of the best parts about Lisp. It's agressively regular, so you'll never have to worry about screwups or ambiguity. And with readtables, the syntax can be more expressive than you might think. Although you did set the standards pretty low...
In the case of code that works with code, rich syntax is a problem, not a solution. Getting rid of tons of syntax is not a sacrifice at all, when you're wrapping, transforming, and/or generating code. And as I've stated elsewhere, the fact that you can do this now so easily means it opens up that power for quick little inline tools, where in most other languages that sort of thing is reserved for major undertakings.
It also eliminates many syntactic bugs, makes searching & editing code easier for humans, makes editor tools easier to create and more broadly useful when they exist, and eliminates ambiguous precedence completely.
You begin to notice that syntax itself really is an anti-feature, after working across a wildly varying range of languages and styles, and designing many fully custom DSLs.
> Getting rid of tons of syntax is not a sacrifice at all
Getting rid of syntax that usefully expresses common cases absolutely is a sacrifice. What was that Turing tar pit thing about making things of interest easy? Lisp is powerful, but its lack of syntax makes it inexpressive.
> It also eliminates many syntactic bugs
How so?
> makes searching & editing code easier for humans
I disagree, code that lacks basic syntax such that programmers end up writing macros, does not make code easier to search and edit.
> makes editor tools easier to create and more broadly useful when they exist
I can see how that would work, because you're writing ASTs like you're a robot, which of course machines can understand better, just not humans.
> eliminates ambiguous precedence completely
That is a totally overstated problem. I've literally never been bitten by this in about 10 years of programming. Everyone in every other language just adds parens by default.
> You begin to notice that syntax itself really is an anti-feature
Having given Clojure I think 'a fair go', I just don't see that happening. Being able to read my teammates code will never be an anti-feature.
Here's the fundamental problem: Not just humans read & write code. Our code also reads & writes our code. If you optimize for the nebulous "human" case, you end up walling off the possibility of having your software join your programming team.
Yet, humans can easily learn both styles. Whatever non-Lisp language you pick is as equally ununderstandable gobbledegook to non-programmers as Lisp. Even COBOL or SQL are way too explicit and finnicky for untrained, non-technical users to write complex applications. There's no difference in technical human understanding barriers between heavily magic-syntax'd code or explicit regular AST-like code. All it is is how used you are to one thing and not the other. (Rich Hickey's notion of the subjective "easy", as opposed to "simple")
Because most languages have traded off for the human-only case, having code writing code isn't even on the radar for most people. Write a few tightly-entwined code generators, and you'll start to see the barriers you hit. Full integration of these concepts opens up completely new productivity and clarity for both you the programmer, and your programming-assisting code, without any difficulties besides the programmer's arbitrary syntactic preference.
if your teammates are careful, and don't go overboard on macros, than their code will be just as readable as it would be in any language. But that's true of any feature. s/macros/(splitting the program into functions|using gotos|building a class hierarchy)/. You can write INTERCAL in any language.
And you keep talking like it's a tradeoff. For you, it may be. But for many of us who write Lisp, the paren-laden syntax is actually enjoyable: it's unambiguous, easy to write so long as the parens match up, and easy to read if you've indented it properly. And if you've got a half-decent editor, it'll indent and match parens for you. With Emacs and paredit, it's even nicer than many other languages: you'll be flinging sexprs around before you know it :-D.
Futhermore, you seem to think 90% of Lisp is macros: in fact, I hardly ever write them. I'm glad they're there when I need them, but when I don't, I write perfectly ordinary code just like anybody else, except that it's more pleasant to write because there's faster iteration, better tooling for manipulating code, and a nice, regular syntax that lets me forget about it, so I can focus on the parts of my code that matter, and get s#!t done.
I don't know ruby, so I can't answer your question directly. But I can ask you one: why not just learn a lisp? You don't have to learn a whole language to know what makes people like it. Working at it for a week or two, in you free time, would probably be enough for you to settle the question permanently. Lisp doesn't have to be a search for nirvana.
Maybe one of these days... I've been spending what time I have for learning completely different languages on Haskell, and I haven't messed with that in a while either.
Meanwhile, I've been using Ruby for a while, and the metaprogramming capabilities are pretty cool. It sounds a lot like what all of the Lisp enthusiasts are saying is so awesome about Lisp to me. So I've always wondered what it is that's so awesome about Lisp that Ruby doesn't already have.