Clerk enables a rich, local-first notebook experience using standard Clojure namespaces and Markdown files with Clojure code fences. You bring your own editor and workflow, your own interactive computing habits, and Clerk enhances all of that with literate programming and rich visualizations.
Inside clj
files, comment blocks are interpreted as prose written in an extended dialect of Markdown. Clerk supports inline TeX, so we can insert the EulerโLagrange equation quite easily:
When Clerk interprets an md
file, the relationship between code blocks and prose is reversed. Instead of the file being code by default with prose in comment blocks, it will be treated as Markdown by default with Clojure code in code fences. Clerk's code fences have a twist, though: they evaluate their contents.
There are loads of other goodies to share, most of which we'll see a bit farther down the page.
You can load, evaluate, and present a file with the clerk/show!
function, but in most cases it's easier to start a file watcher with something like:
... which will automatically reload and re-eval any clj
or md
files that change, displaying the most recently changed one in your browser.
To make this performant enough to feel good, Clerk caches the computations it performs while evaluating each file. Likewise, to make sure it doesn't send too much data to the browser at once, Clerk paginates data structures within an interactive viewer.
As an example, the infinite sequence returned by (range)
will be loaded a little bit at a time as you click on the results. (Note the little underscore under the first paren, it lets you switch this sequence to a vertical rather than horizontal view).
Opaque objects are printed as they would be in the Clojure REPL, like so:
You can leave a form at the top-level like this to examine the result of evaluating it, though you'd probably use your live programming environment to do this most of the time.
Sometimes you don't want Clerk to cache a form. You can turn off caching for a form by placing a special piece of metadata before it, like this:
Another useful technique is to put an instant marking the last time a form was run. This way you can update this result at any time by updating the instant.
Like other objects, UUID
s and inst
s are rendered as they would be in the REPL.
Clerk also supports unicode, of course.
In addition to these basic viewers for Clojure data structures, Clerk comes with a set of built-in viewers for many kinds of things, and a moldable viewer API that can be extended while you work.
Clerk provides a built-in data table viewer that supports the three most common tabular data shapes out of the box: a sequence of maps, where each map's keys are column names; a seq of seq, which is just a grid of values with an optional header; a map of seqs, in with keys are column names and rows are the values for that column.
:sepal.length | :sepal.width | :petal.length | :petal.width | :variety |
---|---|---|---|---|
5.1 | 3.5 | 1.4 | .2 | Setosa |
4.9 | 3 | 1.4 | .2 | Setosa |
4.7 | 3.2 | 1.3 | .2 | Setosa |
4.6 | 3.1 | 1.5 | .2 | Setosa |
5 | 3.6 | 1.4 | .2 | Setosa |
5.4 | 3.9 | 1.7 | .4 | Setosa |
4.6 | 3.4 | 1.4 | .3 | Setosa |
5 | 3.4 | 1.5 | .2 | Setosa |
4.4 | 2.9 | 1.4 | .2 | Setosa |
4.9 | 3.1 | 1.5 | .1 | Setosa |
5.4 | 3.7 | 1.5 | .2 | Setosa |
4.8 | 3.4 | 1.6 | .2 | Setosa |
4.8 | 3 | 1.4 | .1 | Setosa |
4.3 | 3 | 1.1 | .1 | Setosa |
5.8 | 4 | 1.2 | .2 | Setosa |
5.7 | 4.4 | 1.5 | .4 | Setosa |
5.4 | 3.9 | 1.3 | .4 | Setosa |
5.1 | 3.5 | 1.4 | .3 | Setosa |
5.7 | 3.8 | 1.7 | .3 | Setosa |
5.1 | 3.8 | 1.5 | .3 | Setosa |
130 more elided |
Clerk also has built-in support for Plotly's low-ceremony plotting:
But Clerk also has Vega Lite for those who prefer that grammar.
The same Markdown support Clerk uses for comment blocks is also available programmatically:
There's a code viewer uses that clojure-mode for syntax highlighting.
As we've already seen, all comment blocks can contain TeX (we use KaTeX under the covers). In addition, you can call the TeX viewer programmatically. Here, for example, are Maxwell's equations in differential form:
The html
viewer interprets hiccup
when passed a vector. (This can be quite useful for building arbitrary layouts in your notebooks.)
โค | โฅ |
โ | โ |
โฃ | โข |
Alternatively you can also just pass an HTML string, perhaps generated by your code:
In addition to these defaults, you can also attach a custom viewer to any form. Here we make our own little viewer to greet James Clerk Maxwell:
But we can do more interesting things, like using a predicate function to match numbers and turn them into headings, or converting string into paragraphs.
To begin at the beginning:
It is Spring, moonless night in the small town, starless and bible-black,
the cobblestreets silent and the hunched,
courters'-and- rabbits' wood limping invisible
down to the sloeblack, slow, black, crowblack, fishingboat-bobbing sea.
]Or you could use black and white squares to render numbers:
Or build your own colour parser and then use it to generate swatches:
Keep in mind when writing your own :render-fn
that it will run entirely in the browser, and so will not have access to your local bindings on the JVM side. If you need to your viewer to pre-process what it sends to the browser, you can specify a :transform-fn
that will be called before the data is sent over the wire.
Sometimes you might want to create a custom viewer that overrides Clerk's automatic paging behavior. In this example, we use a custom transform-fn
that specifies a content-type
to tell Clerk to serve arbitrary byte arrays as PNG images.
Notice that the image is conveyed out-of-band using the url-for
function to get a URL from which to fetch the blob.
This is just a taste of what's possible using Clerk. Take a look in the notebooks
directory to see a collection of worked examples in different domains.
And don't forget to let us know how it goes!