Clerk: Local-First Notebooks for Clojure
github.com
github.com
It can take a namespace like https://github.com/nextjournal/clerk/blob/fd79f7504ebffaa279... and turn it into https://storage.googleapis.com/nextjournal-snapshots/clerk/b...
The main idea behind Clerk is letting folks use the editors they know & love and not force them to edit code inside a web browser. (This came to us only after working for a few years on nextjournal.com where folks can edit notebooks in a web browser but better late than never :)
In order to keep the feedback loop fast Clerk keeps a dependency graph of all forms and vars in a notebook to figure out what changed between runs on a notebook and only runs those.
There's no homepage for it yet but Clerk is running a handful of sample notebooks on every commit and building a static website out of them. The latest version is at https://storage.googleapis.com/nextjournal-snapshots/clerk/b...
This sounds interesting. Is it essentially an abstraction where to the programmer it seems like the whole notebook runs again for every change? Almost similar to how React makes it seem to the programmer that they are regenerating the entire page?
Specifically Clerk wants to address the following problems:
- Less helpful than my editor
- Notebook code being hard to reuse
- Reproduction problems coming from out-of-order execution
- Problems with archival and putting notebooks in source control
I encounter these problems every time I interact with a Juypter notebook and think about a Clojure style notebook environment :)Great job!!!
I personally can’t use it for production code. But whenever I automate something or write some prototype I use Clojure. Iterations are faster, more direct, less of a hassle.
There has also been someone commenting around here that they use it as an network repl to poke around running production systems, which I find an interesting use case.
- Scrape webpages with http-kit and Specter
- Visualizations with Quil
- Serve static pages with Ring, Compojure, and Hiccup
(un)Fortunately, I don't write Clojure professionally, so I can get away with liking it for aesthetic reasons as much as anything. It definitely does feel like swimming against the current sometimes, though - one runs into bus factor issues a lot in the Clojure ecosystem. But hell, I love it all the same :)
Personally, I am really excited for Clerk - this looks like a really useful tool and I'm looking forward to trying it out!
That's to evade having to use Java, Scala or Kotlin.
Clojurescript: because it's the easiest (compared to Elm, Purescript and Rescript) to call JS.
Concurrency is not the only, or even primary, benefit of immutability in software. State and state management is the number one driver of complexity in software, and complexity itself is the number one problem that makes building software hard. [0]
> For me, there was little value in using it over nodejs for single server CRUD apps with postgres.
You can use clojure on nodejs with the value that clojure is a much, much better language than javascript.
Sure Clojure is a better language but not better enough to justify the additional abstraction layer IMO.
Speaks for yourself, I'd take a pay cut to be able to use Clojure over JS (don't tell anyone )
Some of the why that is is captured here: https://gist.github.com/didibus/6e5ff960b25fdc1a5f8f97acede6...
But understand that a lot of it is about what language you personally enjoy and prefer, Clojure makes programming 10x more fun for me, and I already am someone who finds programming in general fun.
Clojure's sequence abstraction also leads to a very rich and powerful set of core functions that make transforming your data a breeze. Other languages can do this too, but in Clojure I find its just really easy and natural since its part of the language's core library.
It also helps emphasize pure functions: data in, data out. Which makes my code much nicer.
Besides that, I use clojure because I like reitit and spec/malli for writing my HTTP routes, and hugsql for writing my SQL, both much better than the equivalent libraries in other languages I use.
I also like its emphasis on data first, code second. Sure you can do this in other languages too, but Clojure encourages it since its something the community has rallied behind.
Since we are talking about single-threaded single stack context, immutability wouldn't have saved you here, even with immutability you could have receive the wrong value/data from some function in another module down the call stack. The problem was not mutation but a function returning bad output.
You are implying some code modified data underneath you, which can't happen in a single thread.
> You are implying some code modified data underneath you, which can't happen in a single thread.
Sure it can. I mean, yes, the code is ultimately called by my code, through some deep transitive dependency, but the point is that if you have a complex architecture where dependencies interact in complex ways, things can be mutated by a nested function somewhere deep in the call stack. Immutable data would prevent this, unless the data is stored in a mutable object, but in that case, its easy to audit your mutable objects to see where they are used, accessed and mutated. In a language without immutable types, any assignment operator could potentially be mutating something that you don't want it to mutate.
Besides, many otherwise-single-threaded applications we develop these days do have ways that things can mutate asynchronously: request handlers, timeouts, animation frames, network response callbacks etc. It doesn't literally have to mutate them in parallel with other code I'm executing for it to become a problem, just when you flatten a particular execution chain the data may have changed without your knowledge between two steps. For example: I do a network request to an API and wait for the response, has the data been mutated when the response callback is run?
It may even be perfectly valid to do so, my main concern is that it should be explicit and carefully managed. If data defaults to immutable and mutable data requires some extra ceremony (ie deliberate intent by choosing to use an atom or other mutable type), then this signals where care must be taken and makes it clear where integrity may need to be checked, data may need to be reloaded or recalculated etc. Everything else is guaranteed to have the same value you left it with, no matter what, even if it was passed by reference to a callback or deeply nested function call.
Anyway, I was just pointing out why I miss Clojure's data structures when I use other languages and in what cases Clojure would have saved me from headaches. YMMV and all that.
Professionally, I write backend services (mix of micro-services and bigger SOA ones) of various types. Some command lines, some scripts, a few web apps.
In my spare time I use it for random things, mostly stuff I mess on, personal scripts, doing programming challenges, websites, data exploration, etc.
You can use Clojure with NodeJS as well using ClojureScript by the way, or NBB if you want something more straightforward for writing NodeJS scripts.
Immutability benefits are first and foremost about semantics in most cases.
Here is a bit more info regarding it itself. https://orgmode.org/worg/org-contrib/babel/languages/ob-doc-...
See the following gist: https://gist.github.com/adam-james-v/f4d2b75a70b095d14a351a1...
And example code: https://github.com/adam-james-v/bb-draw/blob/main/bb-draw.or...
Jokes aside, good work.