Using Clojure in the command line with Babashka
karimarttila.fi
karimarttila.fi
It's not a "first" language like JS, Python, Java, C#, Ruby etc. so the community appears to benefit from a higher than average level of skill. In the same way that i imagine Erlang or Elixir might enjoy the benefit of more capable than average users.
The fact that entire paradigms can be implemented as libraries is attractive to me - it suggests to me that the Python 3000 or C++11 problems are unlikely to affect Clojure.
For example, Go is a language I find interesting. The whole CSP concept makes a ton of sense to me in writing enterprise software - mainly from a "keeping the architecture cheap to own and change over time" point of view. I appreciate this isn't where CSP was targeted but it lends itself very well IMO to solving this problem because you could decouple components and grow them independently so effectively.
Go is an entire language but core.async is just a library in Clojure, they didn't have to change the language to implement this which is mind blowing to me.
Another example is types. Only some of the code I write actually benefits from types AND most type systems in "enterprise" languages are pretty anaemic. Typing is also really hard to do well, like really really hard. You had a problem, you adopted typing and now you have two problems is generally my thinking here.
Scala was to be my saviour here but that missed the mark quite spectacularly and the compiler is still ridiculously slow today but then along came TypeScript which is pretty darned awesome IMO.
Thanks to TypeScript and modern Python i had learned that i wanted "optional typing" and i was undecided about type erasure at runtime - i don't immediately see a problem with it, i've written probably thousands of lines of Java with generics then used reflection at runtime without losing sleep, but it fills an awkward space in my head - to invest in types but only benefit at compile time seems like a miss.
In Clojure i can follow the TypeScript model but actually take it further to runtime.
This post is too long already. Long way to say Clojure appears very interesting.
You're in love
Ultimately, however, I largely gave it up because the library ecosystem just doesn't feel quite right to me---at least at the time I was writing a lot in it (2016-7ish), most of the popular libraries seemed to have a very strong refugee-from-Java sense to them, as in, very influenced by ideas more commonly seen in oo-land. For example, people loved to use (and, more importantly, bundle in other projects) libraries like component/mount that attempt to implement ideas like dependency injection and other design pattern/architectural concepts that just... don't really feel lispy. I remember trying to build a fairly simple web application with one of the main frameworks and just having no clue which piece of the hyper-complicated and indirect code that the lein template built for me I'd need to change to debug some problems with the database. Alas.
(To be clear, I'm not saying this is wrong! It just wasn't for me.)
Shadow-cljs [0] makes interop with the NPM ecosystem a breeze, and ClojureScript is stable, mature, and feature-complete in its own right.
Although you lose out on some of the features of the JVM (threading), you get access to a lighter weight runtime, and the massive world of JavaScript libraries (which don’t bring much OOP baggage with them).
I’m not sure how viable it is long term, but so far it’s been fun, and pretty trivial to write simple wrappers over JS libraries (when repeated direct interop doesn’t make sense).
Somebody else in here mentioned that Clojure isn’t a “first” language, but I think if there was an “easier” (yes, I said easier, not simpler) path to get into it, more people might pick it up as their second language.
More recently, clojure.spec was released (https://clojure.org/about/spec) which I see the community starting to coalesce around. It’s a nicely-designed way for doing validation and property verification at runtime, and imo avoids the “awkward space” problem you mentioned. By creating specs, you get validation, nice errors, and generative testing (almost) for free. Because Clojure development is often repl-driven anyway, there’s not a huge benefit in having compile-time types, at least if you subscribe to the philosophy behind clojure.spec.
The first chapter ticked a lot of boxes for me - i get a strong impression that the “Clojure way” is pragmatism over ideology. I like that.
When you are working with types, it's like you are working out a complex puzzle. Each time you've solved a part of the system you've created more puzzle pieces in other parts. Solving this meta-puzzle feels nice and productive, especially once you get the hang of it. But it's not really solving the original problem, it's just managing the accidental complexity.
When you’re changing some code that’s been around for a while, or writing adapter type code, or any kind of SERDES type operations, it begins to feel less like helpful guard rails and more like unproductive yak shaving. After the initial creation, most of my code changes fall in the latter camp.
I definitely think having a comfortable way to say “here, in this code, i want types. Over here, i don’t care” is a control missing in many languages. You can certainly fake it (e.g. string typing or everything is object type) but not comfortably like for example in TypeScript.
So there are two parts to the solution: The first part is Small Clojure Interpreter (sci) which is a "(subset of) Clojure written in Clojure", so kind of McCarthy for Clojure. A cool project in itself but it still needs a VM to run.
So here is where the second part comes in: the GraalVM produces native binaries from various things, including Clojure code and SCI is simple enough to work with GraalVM.
It's cool how these two pieces of tech combine together to create something greater than the sum of the parts.
If you are running Clojure you need to run at least clojure.core. Loading that is handled - surprise - by the JVM. As would be expected, if you run other implementations (like Dalvik), this can change dramatically (for worse, usually).
So it is correct that loading the JVM itself is not what's taking most of the time. But loading take so much time because of the JVM.
Here's a pretty good (if a bit outdated) analysis on Clojure startup time: http://clojure-goes-fast.com/blog/clojures-slow-start/
The question is, could Clojure be rewritten in a way that loads significantly faster with the same behavior on the JVM? I'm not sure.
And especially on big projects, the lack of DCE on the JVM can lead to loading a ton of useless code that significantly impacts startup time outside of the Clojure language.
I think this is a good thing! Though I do worry a bit that Clojure can look more moribund than it is. I see a lot of people exploring the language for the first time saying "this tutorial is from 2013, is it still valid?". The answer is mostly yes, but with some changes in tooling that can be dispiriting for beginners, and may make them think the whole ecosystem has gone rusty.
Macros can do a lot of stuff, and as such libraries themselves can add pretty elaborate functionality.
Having said all this, what I do notice with my experience of Clojure is that there are certain “trends” in how you write code. The language itself may not evolve that much, but the styles people write code in it, and how they solve problems, certainly does.
It is built up from SCI (small Clojure interpreter). Which is a Clojure interpreter written in Clojure. That's super cool!
Sci is awesome, and it allows you to create custom Clojure interpreters with whatever subset of Clojure + any additional functions and macros of your liking.
ps: just installed it on my old core i3.. and omg, this loads faster than sbcl.
> It’s nice to have another scripting tool in my toolbox: Babashka. Time will tell if I start using Clojure instead of Python as my preferred scripting language, thanks to Babashka. At least in this exercise Babashka did really well.