I don't understand why Lisp advocacy has evolved to be ineffective in most cases. (While I do understand why Haskell advocacy is ineffective.)
I normally read all the Lisp advocacy pieces that come across me. But it wasn't until my Rust poser phase, where I read Rust articles before playing seriously with the language, that macros became really attractive to me.
The Lisp advocacy I've read mostly revolves around:
- How many parts of the language which normally are core language features, in Lisp they are stdlibby. This is very neat, but mostly the advocacy shows control structures I already have in most languages. I can see the appeal of being able to tweak those to my liking, and introduce new ones. Likely I'm a Mort and my opinion is worthless, but that's rare for me, and when it happens, I am used to say, oh, well, that's life.
- How you can DSL all the things. Neat, but the examples are rarely compelling.
- The simple syntax, which a ton of people reject.
Whereas, when I read about Rust stuff that uses macros, I think they're very neat, but they also address pain points or bring me really nice comforts. All the formatting macros in the core Rust stdlib bring verification of the format string, which is something you hit constantly. The
dbg!
macro is fantastic. A ton of Rust libraries just revolve around macros and bring you new useful things (serde, xshell, clap are the ones that come to mind).
What is weird is that likely all those features exist in Lisp, but somehow I think I rarely read an advocacy piece that mentions them!
(Mostly I'm focusing on static checking- but as it has been mentioned, that can be done in Lisp too! Rarely see examples of that in Lisp advocacy either!)
So, here's the thing: when Lispers talk about how the language's syntax is mutable, they are talking about, among other things, macros. It's just that, given a language with inherently complex syntax, the role of macros as a mechanism of providing syntax is... less clear. As such, until someone bakes in lisp for a while, they don't quite understand what mutable syntax really means from the same perspective. So, the full message of how all this ends up being really really useful for all sorts of tasks depending on what a particular program / programmer values isn't obvious until you know.
Given the direct link between syntax, semantics, and built in data structures provided by Lisp, there are all sorts of amazingly powerful features which are all relatively easy to implement. This can include macros that make specific problems easier to solve correctly, making specific linters (such as a type checker) relatively easy to create, removing duplicate code, etc. It's such a flexible tool* that it's really hard to convey just how useful it is to someone unused to it. It's sort of like trying to explain how useful a smart-matter multi-tool is to someone who is an expert with traditional hand tools, those hand tools solve most of his problems quite readily.
That said, for examples,
dbg
is a pretty good basic obvious example. I'm not sure I have ever worked in a Lisp environment that didn't have it. Then you have macros built around resource management such as
with-open-file
. Then you have threading macros such as
->
, which can be used to write imperative code. There's a whole host of various macros for flow control and error handling. But, honestly, the biggest problem is that the list of uses are so incredibly common that you probably don't even notice it when you use a macro in Lisp, I mean, variable assignment is typically just a macro. Once you get used to that kind of flexibility, not having it is a bit like having a arm chopped off.
I suppose that the short version is: you remember every time you used a source code generator for something it was made for? Magic isn't it? And how, not-so-occasionally, you will find yourself doing something that is almost-but-not-quite what that source code generator does, so, you have to do it the hard way and spend hours writing out almost the same code over and over again? Well, in Lisp, thanks to Macros, that never ever happens, because, in Lisp, it is fairly easy† to write a new source code generator that does just exactly what you need it to do. Better, the base syntax of Lisp is so very simple and uniform that said new source code generator just works.
* Incidentally, this is why you rarely see static checking, especially as seen in C-like type systems discussed. Those type systems are typically so inflexible that they are crippling. As such, fairly few lispers want to work under one unless they are doing something where the code generation benefits are worth it, so, in that category of type checking, you get XTLang and nothing else. Of course, you will see some H-M type checkers, but, then you are basically working in Haskell.
† Sure, it takes a little knowledge to make it all work correctly, hygiene is critical. But, honestly, unlike C/C++ macros, this isn't rocket science, even fairly junior programmers can get it write given a little guidance and know-how.
So I'm perfectly convinced that other than the syntax, which is a very personal choice*, Lisp can be great. And I think, other than the syntax, it's easy to write very readable declarative Lisp, and that you can give the average Joe the proper abstractions, as libraries, to do that.
This is where you are wrong: the simplicity and uniformity of Lisp's syntax
is the secret sauce. It is what both super-charges macros and also makes them fairly easy to write. Because it is trivially parseable, it means that it is comparatively trivial to do complex rewriting in macros which makes the function comparatively powerful. Given something like Python syntax, and suddenly the task becomes much harder. Even Ruby syntax makes it difficult (though, Ruby provides other nice tools to achieve similarish results). Given something like Perl's or C's syntax, and you get a nightmare of buggy unreliable code out of it.