The fascinating thing to me about Clojure is that it's so vocally a "practical" language, and yet is a) a Lisp, and b) purely functional[1]. Usually those two things are associated with language enthusiasts, and not """pragmatic""" development. In the end it works, clearly, it's just very interesting to see that juxtaposition.
I think it says something interesting about the practical value of higher-brow programming concepts, but also about how important the packaging/marketing/ecosystem is to conveying that value and making it accessible to the masses.
[1] I know it's technically not 100% functional, but all of its messaging highlights that philosophy as a focal point and advantage.
I think the most important part about Clojure is how ridiculously straight forward it is.
Once your REPL is set up and you got used to the predominantly functional API and its vocabulary, you are dealing with an extremely simple, data-driven environment that (almost) never gets in the way.
Part of that is due to deviations from traditional Lisps: a more human readable API (first, rest etc.) and less homoiconicity (data structure syntax). But also things like the first class support for names, a baked in, dead simple STM...
For me, this is the reason why it is so practical. I'm not good at top-down programming. I can think of and design a system or solution in an abstract way to get a sense of direction.
But when it comes to writing code I have to look at small, simple things in isolation, look at the results, change the shapes and build something up.
Clojure is very, very good at enabling this kind of dumb, simple, learning-by-doing approach of programming, because everything just works, is easily decomposed and merged together again, with very little ceremony or friction.
And when the simple approach reaches its limitations, then there are these extremely expressive tools like multiple dispatch, macros, transducers, spec etc. which all go beyond what most mainstream languages give you.
This is a minor nitpick but please check your facts about other Lisps when comparing them with Clojure.
> Part of that is due to deviations from traditional Lisps: a more human readable API (first, rest etc.)
first, second, rest ... are defined in Common Lisp
> and less homoiconicity (data structure syntax).
Clojure is no less homoiconic, you just write vector literals in your code; you could write vector literal to represent code in Common Lisp too, but in fact [], {} etc. are not used by default in CL because they are reserved for the user, so that it can be used for domain-specific purposes.
Thank you for those corrections. My limited Lisp experience before Clojure was with Scheme a very long time ago. I shouldn’t have made these false, blanket statements. Those can be harmful for discussion.
In fact I know from reading about CL that it is extremely malleable and that you would be hard pressed to find features that are not present or possible there.
Can someone explain to me why we need a murky word like "homoiconicity" and then explain it as "data structure syntax" (which doesn't clarify much) when "self-modifying code" and "metaprogramming" are crystal clear?
Homoiconicity means you're code is defined in terms of the same data structures that are used in programs written in the language. This makes self-modifying code and metaprogramming easier because the language's standard library is already full of functions that operate on those data structures.
However, you can have self-modifying code and metaprogramming without homoiconicity, and you can have homoiconicity without support for self-modifying code and metaprogramming.
Well, it depends on your exact definition of "self-modifying code" and "metaprogramming", given that it's not like there's an authoratative source you can look up the definitions in. The way I understand the term without additional context, XLST is not "self-modifying code" in the same way malware, certain JIT implementation techniques etc are.
Clojure isn't actually a purely functional language. It's more of a consenting adults language that guides you towards doing the right thing by default. If you write idiomatic Clojure code, then it will be written in a purely functional style. However, Clojure won't stop you from writing imperative code or adding side effects if it makes sense in your particular scenario.
Lisp family of languages also have a lot of tangible and very practical benefits. The syntax is simple very regular, so there's less mental overhead involved in reading it. This helps avoid errors because there's less ambiguity about what the code might mean.
The syntax also makes relationships in code explicit, making it more scannable. In most languages it's not immediately obvious if the lines next to each other are related. On the other hand, s-expression syntax effectively provides you with a free diagram where you can see relationships visually.
The syntax also facilitates structural editing. Instead of working with lines of text, you work with expressions, and you think in terms of moving blocks of logic around. You can see some examples here https://calva.io/paredit/
The syntax also makes it very easy to serialize code, since code is expressed as plain data structures. This also facilitates powerful metaprogramming facilities. You can apply the language to transforming any code written in the language.
Another practical aspect of Lisps is live coding workflow where you connect the editor to the running instance of the application. This creates a very tight feedback loop where you can try things and see the results immediately. This is strictly superior to the traditional workflow where you have to restart the application and build up state to see the results. It's also much faster than using TDD. You can use the REPL for exploration, and then write tests once the code is in the state where it's doing what you want. This is a good read about the workflow https://vvvvalvalval.github.io/posts/what-makes-a-good-repl....
Here's a talk I co-presented last year that discusses an application my team works on and the specific benefits we found from using Clojure https://www.youtube.com/watch?v=IekPZpfbdaI
"... consenting adults that guides you towards doing the right thing by default..." really hits the nail on the head. I do a lot of Java interop at work, which is a necessary evil, but once I'm in Clojure code I'm happy to be gently pushed to be more functional, immutable, have less state, etc etc.
Also, thanks for all your great work Yogthos, Luminus is an amazing contribution to the Clojure ecosystem as well as all your other libraries!
If you read the history, you'll see that small scripts was never a use case targeted by Rich Hickey. It was focused on information systems programming, where a few seconds of startup time is a blip on the full running time of the application.
That said, slow startup times are only true of Clojure JVM. ClojureScript and Clojure babashka, for example, both start fast enough for small scripts.
Also, seems the JVM is slowly improving in startup time department. I was surprised to see that a hello world is now down to 800ms start time on my 2011 laptop with Java 14. It was 3s before with Java 8 on the same laptop.
For comparison, ClojureScript hello world was 550ms, and babashka hello world was 36ms. Python hello world was 68ms and Ruby was 111ms.
It's true that Java hello world will be faster than Clojure JVM hello world. That said, a big Java application with lots of classes to load, and lots of static initializers (which would approach the same number as what Clojure requires to be loaded and initialized) would take as much time as it does for Clojure.
So when people say that part of the slow startup is Clojure's fault, they mean more that it is implemented in a way that the JVM isn't very fast at starting. But if you look at Clojure implemented on other runtimes, like ClojureScript, the startup times are much faster, because the NodeJS runtime is much faster to start and initialize everything than the JVM is.
In effect, if the JVM improves startup times, so will Clojure JVM's startup times be improved. That's why I see this drastic difference between a Clojure hello world on Java 8 vs Java 14.
Startup time was also a deterrent for me initially, but that was ~7-8years. I annoyed the heck out of me.
I was doing Rails back then and that was too much too.
You can further reduce this overhead if you are just using the the Socket REPL, but I think the best support for that (which allows evaluation interruption for example) is the Chlorine plugin for the Atom editor. I usually just accept the overhead of nREPL though.
Startup time for Clojure itself has become pretty fast. My guess is you were using something like lein or boot which involves more than just starting up Clojure.
Either way, for scripting uses there are tools like babashka or lumo with sub second startup times.
I think it says something interesting about the practical value of higher-brow programming concepts, but also about how important the packaging/marketing/ecosystem is to conveying that value and making it accessible to the masses.
[1] I know it's technically not 100% functional, but all of its messaging highlights that philosophy as a focal point and advantage.