AI transcript
Well, okay. I'm glad to be here. My name is Michiel Borkent, also known as Borkdude in the Clojure community. The title of my talk is "Making Tools Developers Actually Use," and some of my projects are actually used, so I'm going to mention those. Babashka, Clj-kondo, and Squint—I'll be revisiting those in the talk.
So at some point in time, Nikita Prokopov said on Twitter to Christophe Grand, who we all know, "I love Clojure. Why is Clojure not more popular?" And Christophe said, "My theory: it's really different than popular languages. Each major difference is a barrier to learning and adoption. So with any barrier, it's easy to see the problems in front of you but hard to see the benefits."
And my question here is: can we do something so people see the benefits of Clojure sooner, before they hit all these barriers?
One tool that changed how I used Clojure was Reagent. Reagent is a library that got introduced in 2014, and it was a wrapper around React. I was pretty—to be honest—I was pretty scared about front-end and browsers. I just didn't want to touch that, especially not Internet Explorer 6 and stuff like that. I'd rather just do something on the back end. I'd call myself a back-end developer, but not because I was good at back end, but just because I was afraid of front end.
JavaScript also wasn't very attractive to me. I thought I'd go insane if I touched that language. But then came Reagent, and Reagent took concepts from Clojure that I already knew—namely functions, hiccup, and atoms—and it showed me you can actually build a front end with the things you already know. You don't have to learn anything new. And then this got me doing front end.
So what Reagent did was take away my fear of the front end and make it really intuitive. It lured me into doing front end. And half a year later, after using Reagent, I was giving talks about Reagent at a conference in Sweden.
After using Reagent for a bit, I started to wonder about React and JavaScript. And maybe things weren't so bad as I thought after all. Well, they were, but I got used to them.
If you want to make a tool that developers actually use, lower the barrier to entry. Reuse familiar concepts—like in Clojure, I knew the atoms and hiccup and functions. And you can overcome initial fear by making it look intuitive and easy. The tool should say to you, "You can actually do this."
I don't even have to explain that in this room, I think, but all the Reagent examples from 2014 still work today. That's unbelievable in the JS ecosystem, I would say.
Reagent doesn't make you use all the difficult concepts from React all at once. You can start simple and write basic UI elements, and then later on maybe you should learn about the DOM and React hooks. I still—I never use React hooks, to be honest. I just write Reagent style, 2014 style.
Now put yourself in the shoes of someone who is trying to learn Clojure. You are a TypeScript developer trying to learn Clojure. There are so many things to learn here. Like the REPL—of course you should learn the REPL—but there are so many other things. Which one should you pick first? Should I use linting? Should I use deps.edn? Why is laziness biting me if I use dynamic vars? Where to start?
Can we build some tools to meet developers where they are?
I wrote a tool called Babashka. What Babashka does is you can write scripts in Clojure. They start in a few milliseconds like bash scripts, and it's very lightweight. You can use like 95% of what you're used to from JVM Clojure. The target audience here is JVM Clojure developers, and what this replaces is bash scripting.
What this tool does is make scripting not so scary anymore. You don't have to do bash syntax. I never remember nested if-else syntax in bash, so I'm happy I can use Clojure here.
The lesson here: If you want to make a tool that is very similar to another tool, you should do one thing really different or better—which is startup time here.
Two weeks ago I found a comment on Hacker News. It was from someone who said, "I started writing way more utility scripts when I found Babashka. The magic of Clojure: instant startup, easy to shell out to other commands, tons of useful built-in stuff, developing with the REPL. It's just a good time."
This is like the best comment I can get for Babashka. This is exactly what it was made for. And then a week later he also said, "As an anecdote, about a year ago I realized I was not having that much fun learning Rust," and he landed on Clojure, and "as a starter, Babashka is a good way to learn the language."
What Babashka does for him is introduce him into the Clojure ecosystem using something he is familiar with, like writing scripts. And then once he is using Babashka, he's discovering the rest of Clojure. He made a lot of fun utilities. Fun and learning—those are the central words here. And he even got his co-workers to use his scripts in Babashka.
One example of a Babashka script: here we have a script that makes it really easy to use SQLite. If I give you this script and you have Babashka installed, you can use SQLite on your machine without installing anything else.
Babashka has a concept called pods. Pods are like a library as a binary. When you write load-pod with some qualified name and a version, Babashka downloads this helper binary which contains SQLite, and then it registers functions in Babashka that you can call via RPC. After calling load-pod, you can require the namespace that already exists in Babashka, and then you can say sqlite/execute something. You can even use HoneySQL here as a library to generate a query. That doesn't matter because eventually it's just a string, and it just works.
What I learned when developing pods: you should make it really easy for people to obtain these pods, because if you make them install via some other package manager, they just won't use it. It's just a barrier before they're going to use it. Only when we made running these pods that easy did people really use them.
And this is fast too. Running this script takes 60 milliseconds to start the pod and do some queries, which is competitive with a Node.js script. And with Node.js you have to install libraries from npm—here it's just an all-self-contained thing that I can hand to my coworker.
Some people, for some reason, want to avoid the JVM. They do exist. And some people are just more familiar with the JavaScript ecosystem—they like to reuse their language also on the back end. For those people, NBB is a similar idea to Babashka but for Node. So it's "Node Babashka."
When you have npx installed or npm installed, the only thing you have to do to get a Clojure REPL is type npx nbb, and then you get a Clojure REPL. There is no further installation needed for this. You can start playing around with Clojure and write some scripts.
This is meeting Node.js developers where they are. They're used to writing scripts, and now they can play around with ClojureScript without installing anything. That might be a nice way to get them hooked on using ClojureScript for the front end, for example. It supports nREPL of course—you just type npx nbb nrepl-server and it starts an nREPL server and you can start hacking from your editor.
Scittle is the same idea—it's scripting, but now in your browser. You have an HTML page and a piece of ClojureScript. That's scripting too, right? But just in a different context.
What you can do is include Scittle from a CDN in a script tag. And then we add a Reagent program—the same Reagent program you saw on one of my first slides, the counter example. But now we can script directly in our browser without installing any dependencies. It just directly works. And of course it's not so convenient to write code in an HTML page, but you can put this in another file and even connect with nREPL to your browser without doing any configuration.
There was a company who was building their UI for a transport logistics company. They bootstrapped their entire product UI in Scittle. I talked to them—shouldn't you move to Shadow-cljs or something? "Well, no, we're actually fine. We couldn't figure out Shadow-cljs just yet." They had been running for a year only on Scittle before they even migrated to something else. If Scittle didn't exist, they might have gone with TypeScript. Who knows? So this was just a good intermediate solution for them.
Gijs Stuurman, who is here with us today, wrote a series of blog posts on Scittle and introduced Scittle as something that "eliminates the build complexity that often holds people back from creating." That's exactly what Scittle wants to do—it wants you to create something without thinking first about the build complexity.
This is another example. A fellow Dutch Clojurian was on a boring train ride through Europe, and all he had with him was an Android phone. He installed the Userland app, which allowed him to run Emacs on his phone. He had just watched some YouTube videos about how to make a pinball game with some physics rules, and he started hacking on his phone and created this entire pinball game in Scittle during his boring train ride. He wrote a blog post about this—you can just Google for "pinball Scittle."
The lesson here: Easy and fun really matters. If you make something easy and really fun, people start playing around with their ideas, and they might look further than this initial playground idea.
Error messages are of course important in every language, and something where Babashka tries to do something more than regular Clojure is point you to the location of the error message in the source code. If we have an expression that's missing the last closing paren, Babashka points you to the source code. This is something that clj-kondo also does. Actually, this idea came from clj-kondo—clj-kondo had this idea to show the warnings exactly where they are, and this idea was transferred to other tools.
Here we see defn with a docstring in the wrong place. People actually do this so often that LLMs copy this behavior now, and then they get the clj-kondo warning and then they correct it. So it's funny how that works. And it doesn't matter for clj-kondo how much you screw up your parens in the middle of this program—the warnings after that still work. It doesn't say, "You first have to fix this paren here, and then I'll tell you the rest of the warnings."
Now the story of something that is more in the background—it's a tool I made called deps.clj, which started as an experiment. While I developed Babashka, I wanted to know: can I port a random bash script to Clojure using Babashka? As an example, I took the Clojure CLI bash script, which is written in bash, and I started porting it line by line. It was just manual labor that took me a few hours during a Christmas break, and then I had the Clojure CLI working in Babashka after I fixed the bugs in Babashka—because I was still bootstrapping it.
Then I had this script, and I thought: what can I do with a Clojure program? Of course, using GraalVM, we can compile any Clojure program to a standalone executable. So I thought: now we have the Clojure CLI. What if I make it automatically download everything that is needed when you first use it, and then make this executable from it, and then I give this executable to a coworker and seduce him to run the binary, and suddenly he has a Clojure REPL too? Maybe that would work.
Meanwhile, in Windows land, people were having trouble with the Clojure CLI that was written in PowerShell, because PowerShell modules—you can't really execute them from cmd.exe if you don't go through PowerShell first. It's not really a first-class citizen from all angles in Windows, especially when you shell out to the Clojure CLI from other programs. You always had to special-case Windows because it was this special PowerShell thing. And some people couldn't make PowerShell work, or their company forbade them to run PowerShell.
So I would seduce them: maybe try this binary. And in some cases, in restricted environments, they could still use the Clojure CLI that way in Windows. And this worked so well that a few years later, this became the official way of installing Clojure on Windows—that's now called clj-msi, which is basically deps.clj wrapped in an MSI installer.
Meanwhile, in Calva and Cursive, they took deps.clj as an uber jar as a fallback method. When users wouldn't have the Clojure CLI installed, they would just run this uber jar to get people's project started.
The thing here is: I thought I was just porting a bash script to Clojure, but it had this surprising effect later that it turned out to be useful in other contexts. And if I wouldn't have shared my experiments around with other people, I probably wouldn't have taken it this far.
The lesson here: Share your experiments. Share your scrappy fiddles. There's actually a talk about this idea at Heart of Clojure last year by Lu Wilson—go check that out as well.
The other lesson: Absolutely eat your own dog food. Test your own tools on things you see around you. When somebody creates a new library in Clojure, I test if it works in Babashka, and if something is missing in Babashka, I'll fix that. Currently, 106 libraries from the Clojure ecosystem are tested in CI of Babashka in every pull request that I make. So if I change a little thing in Babashka and something falls over, I know it before they know it.
Clj-kondo does a similar thing—it runs libraries as regression tests. A code base might have like 10 expected warnings, and I don't want those warnings to change if I change clj-kondo.
What is Squint? Squint is a ClojureScript dialect. It's not a dialect of Clojure—it's a dialect of ClojureScript. So it's a dialect of a dialect.
The idea behind Squint is that you can create very small JavaScript bundles using idiomatic Clojure code. How does it reach that goal? It does it by using JavaScript data structures directly. You're not living in two separate worlds—the ClojureScript data structure world or the JavaScript data structure world. They're just the same. This has benefits and it has drawbacks, but in many cases this works well for certain programs.
Here's an example of the same Reagent program that I showed in the beginning, but now using a library called Reygami that I wrote last week, which is a DOM patching library. It works like Reagent, but it's only 5 kilobytes gzipped. This whole example, if you first optimize it with esbuild and then gzip it, is just 5 kilobytes gzipped.
What makes Squint special is that you're now a first-class JavaScript citizen. You can publish your libraries to npm, and other JavaScript people can use that without bringing a whole standard library that's very big with you, because the standard library is also just a library on npm that is shared across all projects that use Squint.
One example is cljdoc, which is a centralized Clojure documentation website. They first had their front end in JavaScript, and then someone had the brilliant idea to port it to TypeScript, but then some other people started maintaining cljdoc and they didn't understand TypeScript. So they had the brilliant idea to use Squint. That's a joke.
But what they were able to do was port TypeScript one file at a time. Very gradually, they were able to migrate file by file to Squint, and perhaps they can migrate to ClojureScript later. But at least it looks a lot like ClojureScript now, and the bundle size is still very nice and small.
The point here: If you want to climb the ladder towards running Clojure in the cloud—in the dark cloud, somehow, I don't know why the cloud is dark—maybe some tools can help you overcome the initial fear, the initial barriers of setting up a big project and learning all things at the same time, by just meeting people where they are and encouraging them to just play around with Clojure first.
Be contributor-friendly and document your build process. Babashka tasks can help with that.
What was surprising to me: Scittle, for example, has existed for four years, but suddenly in the last three weeks, everybody starts blogging about it. I don't know—this thing has been here for four years already. So it can take four, five, maybe longer years before people actually start picking up on your projects. So keep with it.
Actively gather feedback using surveys. That's actually also very useful, I found. Or just informal polls on Slack: what do you think of this? What do you think of that idea? Should we maybe have a function for this or that? Just involve your users.
Without the community, I wouldn't have created these tools, because that would have been very boring if I would just use these tools on my own. In the context of this community, that's where these tools have grown. And I couldn't have done it without the support—also financial support—for my open source.
I want to thank Nextjournal, Roam Research, Clojurists Together, Nubank, and several other individual companies and individuals. I cannot name them individually—that would take too long—but thanks, everyone.
If you want to have people use your tools:
- Make it easy
- Make it fun
- Have fun making it
- Make it different—it should really have a clear reason why a user wants to use your tool in addition to existing tools
- Make it reliable—because who wants to use a tool that changes every week? That's not fun at all.
Here is a picture of my "Borkiverse," as people call it. Surprisingly, when I wrote SCI, which is the interpreter, I couldn't have predicted that this would be used in almost every project that I have now. It's used in clj-kondo to run macros. It's used in VS Code for Joyride as the Joyride project to script VS Code like Emacs. And it's used in Squint for running macros as well, so you don't have to bring the whole compiler with you at runtime.
I have some future plans. So keep supporting me.
That's my talk.