Journal 2019.19 - Clojure 1.10.1-beta3, JIRA migration

Clojure 1.10.1-beta3

We released Clojure 1.10.1-beta3 this week - please give it a whirl. The only change in it is CLJ-2504 which redoes the clojure.main changes to support different error reporting targets. The most important part of that is that you can specify the target via either a clojure.main option or by a Java system property. The latter is useful because you can take control via existing Java system property support in Leiningen or other tools without needing any subsequent changes in Leiningen.

Specifically, the default behavior for error reporting with Clojure 1.10.1-beta3 is to write the error report and stack trace to a file, but you can also change that to stderr via system property if you prefer.

This change was made in response to feedback on Clojure 1.10.1-beta2, so thanks! We plan to move pretty directly from here towards an RC and release, so give it a try.

JIRA migration

I have spent a fair bit of time this week migrating our aging JIRA system to a new cloud hosted instance. That migration is now largely complete from a data perspective - users, projects, issues, attachments, etc have all been migrated. I am still working on many of the finer details (important reports, permission schemes, screen setups) and new processes for filing issues (won’t require an account) and becoming a contributor. Thanks for your patience, it’s been a slog but this will be a big help moving forward.

Other stuff I enjoyed this week…

I was very fortunate in the past week to see two bands that I love for the first time. First, I took a jaunt out to Denver to catch Vulfpeck at Red Rocks. It was cold and a bit snowy, but it was a fantastic show, including a bonus set from the Vulfpeck spinoff Fearless Flyers! I really enjoyed the opening set from Cory Henry - funk organ trio and his guesting during Vulfpeck’s set. There were so many great songs in the show but probably the high point for me was Wait for the Moment featuring Antwaun Stanley on vocals and Cory Henry guesting on organ. Beautiful, amazing version.

And then this week I got to see Tool back home in St. Louis. Great show and enjoyed seeing a couple new tunes from their (finally) upcoming album - end of August it looks like. Both new songs were predominantly in 7 and they had a large 7-pointed star in the set, so maybe a theme. My favorite album is Lateralus so I probably enjoyed Parabola and Schism the most.


Ep 029: Problem Unknown: Log Lines

Nate is dropped in the middle of a huge log file and hunts for the source of the errors.

  • We dig into the world of DonutGram.
  • “We are the devops.”
  • Problem: we start seeing errors in the log.
  • “The kind of error that makes total sense to whoever wrote the log line.”
  • (04:10) We want to use Clojure to characterize the errors.
  • Why not grep?
  • Well…we grep out the lines, count them, and we get 10,351 times. Is that a lot? Hard to say. We need more info.
  • (05:50) Developers think it’s safe to ignore, but it’s bugging us.
  • We want to come up with an incident count by user.
  • “Important thing we do a lot in the devops area: we see data and then we have to figure out, what does it mean?”
  • “What story is the data telling us?”
  • The “forensic data” versus “operating data” (Ep 022, 027)
  • “With forensic data, you’re trying to save the state of the imprint of the known universe over time.”
  • (07:40) The log file is the most common forensic data for applications.
  • To characterize, we need to manipulate the data at a higher level than grep.
  • First goal: make the data useful in Clojure
  • Get a nice sample from the production log, put it in a file, load up the REPL, and start writing code to parse it.
  • Need to parse out the data from the log file and turn it into something more structured.
  • (12:05) Let’s make a function, parse-line that separates out all the common elements for each line:
    • timestamp
    • log level
    • thread name
    • package name
    • freeform “message”
  • “A message has arrived from the developers, lovingly bundled in a long log line.”
  • (13:10) We can parse every line generically, but we need to parse the message and make sense of that.
  • “We want to lift up the unstructured data into structured data that we can map, filter, and reduce on.”
  • We will have different kinds of log lines, so we need to detect them and parse appropriately.
  • We want to amplify the map to include the details for the specific kind of log line.
  • We’ll use a kind field to identify which kind of log line it is.
  • There are two steps:
    1. recognize which kind of log line the “message” is for
    2. parse the data out of that message
  • “Maybe we’ll have a podcast that’s constantly about constantly. It’s not just juxt.”
  • (18:45) How do we do this concisely?
  • Let’s use cond:
    • flatten out all the cases
    • code to detect kind (the condition)
    • code to parse the details (the expression)
  • Can use includes? in the condition to detect the kind and re-matches to parse.
  • (20:00) Why not use the regex to detect the kind too?
  • Can avoid writing the regex twice by using a def.
  • It’s less code, but now we’re running the regex twice: once in the condition and once to parse.
  • We can’t capture the result of the test in cond. No “cond-let” macro.
  • We could write our own cond-let macro, but should we?
  • “When you feel like you should write a macro, you should step back and assess your current state of being. Am I tired? Did I have a bad day? Do I really need this?”
  • (24:05) New goals for our problem:
    • one regex literal
    • only run the regex once
    • keep the code that uses the matches close to the regex
  • Similar problem to “routes” for web endpoints: want the route definition next to the code that executes using the data for that route.
  • Feels like an index or table of options.
  • (25:20) Let’s make a “table”. A vector of regex and handler-code pairs.
  • We need a coffee mug: “Clojure. It’s just a list.”
  • The code can be a function literal that takes the matches and returns a map of parsed data.
  • Write a function parse-details which goes through each regex until one matches and then invokes the handler for that one. (See below.)
  • (30:15) Once we have higher-level data, it’s straight forward to filter and group-by to get our user count.
  • Once again, the goal is to take unstructured data and turn it into structured data.
  • “You have just up-leveled unstructured information into a sequence of structured information.”
  • Can slurp, split, map, filter, and then aggregate.
  • (32:10) What happens when we try to open a 10 GB log file?
  • Sounds like a problem for next week.
  • “Fixing production is always a problem for right now.”
  • “If a server falls over and no one outside of the devops team knows about it, did the server really fall over?”
  • “Who watches the watchers?”

Related episodes:

Clojure in this episode:

  • #""
  • re-matches
  • case
  • cond
  • if-let
  • ->>
  • slurp
  • filter
  • group-by
  • def
  • constantly
  • juxt
  • clojure.string/
    • includes?
    • split

Related links:

Code sample from this episode:

(ns devops.week-01
    [ :as io]
    [clojure.string :as string]

;; General parsing

(def general-re #"(\d\d\d\d-\d\d-\d\d)\s+(\d\d:\d\d:\d\d)\s+\|\s+(\S+)\s+\|\s+(\S+)\s+\|\s+(\S+)\s+\|\s(.*)")

(defn parse-line
  (when-let [[whole dt tm thread-name level ns message] (re-matches general-re line)]
    {:raw/line whole
     :log/date dt
     :log/time tm
     :log/thread thread-name
     :log/level level
     :log/namespace ns
     :log/message message

(defn general-parse
  (->> lines
       (map parse-line)
       (filter some?)))

;; Detailed parsing

(def detail-specs
  [[#"transaction failed while updating user ([^:]+): code 357"
    (fn [[_whole user]] {:kind :code-357 :code-357/user user})]

(defn try-detail-spec
  [message [re fn]]
  (when-some [matches (re-matches re message)]
    (fn matches)))

(defn parse-details
  (let [{:keys [log/message]} entry]
    (if-some [extra (->> detail-specs
                         (map (partial try-detail-spec message))
                         (filter some?)
      (merge entry extra)

;; Log analysis

(defn lines
  (->> (slurp filename)

(defn summarize
  [filename calc]
  (->> (lines filename)
       (map parse-details)

;; data summarizing

(defn code-357-by-user
  (->> entries
       (filter #(= :code-357 (:kind %)))
       (map :code-357/user)

  (summarize "sample.log" code-357-by-user)

Log file sample:

2019-05-14 16:48:55 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user joe: code 357
2019-05-14 16:48:56 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user sally: code 357
2019-05-14 16:48:57 | process-Poster | INFO  | com.donutgram.poster | transaction failed while updating user joe: code 357


Meta Reduce, Volume 2019.2

About a month has passed since the last “Meta Reduce” update I wrote. I’ve kept pretty busy in the mean time and now I have quite a few things to share with you. Where do I begin…



I’ve spent a lot of time lately triaging issues and cleaning up the issue tracker a bit. In the course of this process I’ve identified quite a few good first issues and I’ve labeled them accordingly. I hope that some of you would take a stab at fixing some of them. I keep dreaming of a backlog of less than 50 tickets, but CIDER won’t get there without your help.

I’ve addressed a few small tickets and I’ve implemented a prototype of find references based on ideas I published earlier. The prototype is rough around the edges, but works reasonably well overall. You can try it with M-x cider-xref-fn-refs.

I’ve also played a bit with the Read the Docs setup for CIDER and it now serves the documentation for the latest stable version by default.1


I’ve started a new blog series dedicated to CIDER. Here are the articles in the series so far:

I plan to write many more, but knowing me - I’ll probably fail miserably. Time will tell.

Alongside it, I’ve created a demo project I’ll be using the blog series. It’s basically empty at this point, but I hope it’s going to become an useful reference for newcomers at some point.

I’ve been experimenting with some gif-recording tools around “Hard CIDER” and I really liked Gifox. I’m thinking I might be able to combine it somehow with keycastr for optimal results. As you can see I’d rather have short animated gifs in the posts instead of real videos, but I might reconsider this down the road.


It has been mostly quiet on the nREPL front lately. I might end up cutting nREPL 0.7 soon just to get the EDN transport out there and postpone the other planned features for a later release. There are a few interesting open tickets that are worth taking a look at and could benefit from some help:

I’m especially interested in getting nREPL middleware and client authors involved in those conversations.


Work towards the 0.5 release is progressing steadily. Orchard 0.5.0-beta3 has been out for a while and it seems to be working pretty well.

The planned ClojureScript support might land in 0.5 as well after all. That’s going to be decided soon.


Ruby Style Guide

After a long period of stagnation I’ve started to slowly go through the issues in the backlog and tweaked the guide here and there. My ultimate goal is to go over all the issues and all the existing rules. I want to make sure that the wording and examples everywhere are clear and consistent. More importantly - I want to make sure that the guide is very cohesive and all its parts fit together like a glove.

I also plan to convert the guide to use AsciiDoc, so we can get richer formatting options and publish it easily in more formats.

Rails Style Guide

Andy Waite and Phil Pirozhkov joined the editors team and they have been doing some awesome work cleaning up the guide and updating it.

RSpec Style Guide

Phil converted the guide to AsciiDoc recently and the result is really awesome. I’m super excited about converting the Ruby and Rails guides to AsciiDoc as well.

Special thanks to Dan Allen for his work on AsciiDoctor, (kramdown-asciidoc)[], and the valuable tips he gave us during the conversion process!

Weird Ruby

I’ve started a new blog series dedicated to some weird and fun aspects of Ruby. Here are the articles in the series so far:

I’ll be aiming to write 2-3 each month. Fingers crossed.


RuboCop got two new major releases recently - 0.68 and 0.69. The first one was notable for the extraction of performance cops to a separate gem (rubocop-performance) and the second one’s big highlight is dropping support for Ruby 2.2.

I’ve also played a bit with the Read the Docs setup for RuboCop and it now serves the documentation for the latest stable version by default. In addition I’ve added the documentation for rubocop-rails and rubocop-performance under the main project’s documentation:

I think the result is reasonably good. I hope we’ll soon get some documentation for rubocop-rspec as well.

Apart from that - we’re steadily moving toward the dream 1.0 release and a new era of stability. I think it might happen before the end of the year. The extraction of the Rails cops into a separate gem is the biggest outstanding task.

Balkan Ruby

Balkan Ruby is right around the corner and I’m excited about it. It’s really awesome to host such a great event in my humble hometown of Sofia. Say “Hi!” if you happen to see me there!


I’ve had very little time for Emacs-related work lately (outside the scope of CIDER and friends).

Still, I found a bit of time to go through some Solarized and Zenburn related tickets and cut the first new release of Solarized for Emacs in several years.

In other news I’ve developed a massive time saver when it comes to blogging with Jekyll:

(defun jekyll-insert-post-url ()
  (let* ((files (remove "." (mapcar #'file-name-sans-extension (directory-files "."))))
         (selected-file (completing-read "Select article: " files nil t)))
    (insert (format "{%% post_url %s %%}" selected-file))))

Linking to other articles has never been easier. Got to love Emacs!


All of my major projects (RuboCop, CIDER, Prelude and Projectile) now use stalebot to auto-manage stale issues and pull requests. The current policy is the following:

  • Issues/PRs are marked as stale after 90 days of no activity.
  • They are closed after extra 30 days of no activity.
  • Issues marked as “High Priority”, “Pinned”, “Good First Issue” are never auto-closed.
  • Issues with milestones are never auto-closed.

This setup might not be perfect, but I think it makes sense for open-source projects. We’re using this same bot to great effect on my day job, but there the settings are way more aggressive. I wrote more about the subject here.

In the Real World

I’ve finished reading “Persepolis Rising” and I’d rate it 3/5. “The Expanse” is certainly a bloated soap opera at this point. I’ll need a lot of willpower to convince myself to make it to the next book in the series.

I’ve decided to recover from it with some sci-fi short stories by Philip Dick and William Gibson. Philip Dick was so ahead of his time! I’ve always loved his work!

On the cinema/TV front my life has been dominated by “Game of Thrones”. Like many of you I’ve been outraged by how dark #8.3 was and how the story developed throughout the season in general. I’m fairly certain that whatever ending George Martin has in store for us is going to be significantly better. I just hope I’ll live to read it.

And don’t even get me started on “Endgame”. It was OK, but it felt like some final victory lap, not like a real movie. Not to mention it was utterly predictable (unlike GoT). Still, it was an interesting milestone for the Marvel cinematic universe and I’m curious where does it go from here.

On the side I’ve finished watching the second season of “Star Trek: Discovery” and it was total garbage. They really need some better writers there, as the main story was both pretty dumb and pretty boring. As was everything else in this season…

“The Umbrella Academy” was the next show I’ve started. So far I mostly like it, although it’s definitely a bit too slow paced for my taste. I love the characters, though, and the general vibe of the show. Reminds me a bit of classics like “Kick-ass”.

Time to wrap it up! Until next time!

  1. It used to point to master’s documentation by default. 


Is your layer of indirection actually useful?

There’s a cliche: any problem can be solved with another layer of indirection. That’s true, but does your brilliant idea for a new layer of indirection actually solve a problem? In this episode, we explore this question and develop a rule of thumb for evaluating layers of indirection.


Eric Normand: Is your layer of indirection actually useful? Do you really need that one more layer of indirection?

In this episode I want to incite, inspire, engender a deeper appreciation of indirection and specifically what it’s useful for and when you should avoid it. My name is Eric Normand and I help people thrive with functional programming.

This is an important topic, indirection is one of our most useful tools for having a place where a problem is solved. It doesn’t actually solve it, but it gives you a place to put the solution. It also helps us separate concerns that have a little piece of indirection, a little trick that lets you separate two things.

Also for encapsulating modules from each other so that they can change independently. That’s what interfaces are. They’re like a piece of indirection. Instead of just like, “Oh, I know how that thing works,” and reaching into its memory and just changing a thing. You say, “We’re gonna go through this interface.”

Even if it’s a tiny bit less efficient in terms of runtime, it’s better because I can always change how it’s implemented behind the interface without having to figure out everywhere. That’s a layer of indirection.

It’s useful. I want to start the discussion with the cliché. It’s cliché at this point. That all problems, any problem can be solved with another layer of indirection. I think it’s true. I’ve tried to think of problems that indirection, like another layer of indirection couldn’t solve. Sometimes it’s not indirection itself that solves it but the layer gives a place to solve it.

It gives you a place where it can be solved. Make you go through another not one more little decision, one little if statement more, and I’ll have a little layer where that if statement happens. It’ll just keep everything clean, but it’s got still another layer in there.

That’s important and it’s very useful. The problem is that indirection has costs.

Let’s not even talk about the runtime costs of indirection which are real but in terms of making your code harder to understand, it’s indirection is one of the big deals that makes things hard to understand.

Because if you’re debugging something and you need to know how did this value get calculated and it went through 10, 20 layers of indirection to get calculated, it’s going to be hard for you as a programmer with your…OK, I’ll talk about myself. I have limited mental capacity. I’m already overtaxed with all the stuff I have to think about.

Now I have to trace how this value went through all these different layers, what interactions there were. How did this get decided at each step? It’s a difficult task to do.

If it was all laid out in one place like I said only first to do this and you decide that then you decide that then you decide that and then there you have answer, that would be much easier. Instead, indirection tends to — because it’s separating concerns — spread that flow of control of the data into different parts of your code.

That makes it hard. It also tends to hide problems. One thing that my friend always talks about is how caching, which is the kind of indirection. It hides problems. If you’re getting the wrong answer or something is not looking right on your Web page, I do front end stuff all the time.

Sometimes I’m like this is not the right answer that I expect, given that I just changed the server or what do I do. I’m going to do a hard refresh and say no more caching, just give me everything new. That solves the problem a lot of times, so much so that I think I always hard refresh. I never do like a cached refresh, because I don’t want to deal with that.

If you’ve got caching on, it could be that you’re hiding that problem. You don’t see the problem.

In general, it makes it harder to figure out where the work is happening. In fact, I remember someone talked about in small talk, because they have factored things out into such small units, such small classes that it always felt that the work is always done somewhere else.

Where is the work actually done? It’s always somewhere else. Whatever code you’re looking at, it’s going to send four messages out [laughs] to get the work done. Then you’re like, “OK, let me go look at the code for those methods and you go look at the methods.” It’s like, “Oh, they’re sending four messages out.”

Where does it actually get done? Where does the step-by-step algorithm…where does the work happen? It’s impossible to find. It always seemed like there was another layer of indirection going on. That’s frustrating. That can be tough.

My experience tells me after so many years working in software, working in the industry, is that it is true that any problem can be solved in other layer of indirection. That we need to amend that, which is to say if you don’t have a real problem you should not be adding a layer of indirection.

If you cannot name the problem you are solving with that layer, you don’t need it. I’ve seen people add queues between things for no reason. They build an interface around something for no reason. Those things all add up and make it harder and harder to understand. This is one of the things that happens a lot with the YAGNI principle, “You Aren’t Gonna Need It.”

Very often, us programmers, we feel “I might need this” so let me build it now while I’m here. Usually, you don’t need it. You can imagine all the many situation you might need it or imagine situations where you will need it. They’re all imaginary and they all seem really important.

You’re probably not going to need it and you should wait on it before you add the indirection unless it has zero cost. That’s another, I guess, caveat to that appétit. As an example, in Java, the common practice is you make a class and then you put all of these fields and you make them all private. Then of course, you need to change them so you make getter methods.

You need to get the value from the getter methods. All they do is return the private fields. It’s silly. You just made a private so that you can open it up with a getter.

When you ask a Java programmer why they do that, why do they do all of this work when no one is even calling these methods yet. This is the first time you’re writing the class. You’ve already built all this indirection. What they will say is, one day, maybe, we will need to replace this Get X, which just returns X with some calculation.

Instead of just returning the value directly, we will want to calculate it from two other fields. We don’t want to have to change all of the code that gets the X. We want to keep this level of indirection in this layer of indirection just in case one day we want to change it.

The question is, does the cost of changing the code later justify the cost of adding in that interaction now? Because that’s a lot of code. It’s a lot of code to make all these getters. Each one is three lines of code. That’s an interesting question.

I don’t know Ruby, but I’ve seen that they have a very good solution to this, which is on one line they can list all of their fields and say make automatic getters for these. One line. It’s the same amount of code it is as it is to just make them all private and not have getters. It’s basically zero cost.

They’ve taken this thing that could be a tough thing to calculate how much will it cost to change our code versus how likely is it to need to be changed versus how much code am I generating here? They’ve turned it into like a non-question. It’s the non-issue. It costs no more code to make all the getters than it does to not make them and to access the fields directly.

That’s an interesting trade off. It’s like in terms of the amount of code and the effort it takes. That is a free level of indirection, which approve. That’s great.

In the Java side, I’m thinking what problem is this solving? It’s a problem you don’t have, you don’t have a problem of what’s the way to put it? What problem is that trying to solve is trying to make these…What is the problem that you want to give access to the values of those fields, but you don’t want to let someone change them.

I said, usually people make getters and setters anyway so it’s just unclear. It’s just unclear what the problem is that is being solved. I really look at all these things with a skeptical eye because I’ve done it myself. I’ve over complicated stuff with tons of indirection.

When a simple step-by-step do this, then that, then that was clearly a superior solution after someone showed me. I’ve seen other people do it where I’ve been reviewing their code and I’m like, “What is this?” It’s a queue.

You’re putting something on a queue and then there’s a worker that’s taking the thing off the queue and doing it. Why don’t you just do it instead of putting it on a queue? Do it right there. Why does it go in a queue? The answer is always, “Well, what if we want to add some other kind of work that the worker can do?”

It’s like, “OK, great. Let’s look into the future. What kind of work will we want to do?” They can’t name anything. It’s like, “Well, if you can’t name it now, who’s to say you’re going to be able to name it in the future?” If we will have something in the future that needs this queue, we’ll just add it instead of adding it now we’ll just add it in the future.

What’s the difference? The difference is today we save all of this work and this indirection and maybe we won’t even need it. There, that’s my long rant.

Let me recap. Any problem can be solved with another layer of indirection. It’s true. Indirection can make things harder to understand and hide problems. The problem could be hidden in this huge multilayer cake of indirection.

My recommendation is whenever you’re thinking about adding a new layer of indirection or you’re evaluating an existing layer, you ask, “What is the problem that this is actually solving?” It could be very neat. It could be a nice pattern, a very common pattern, a design pattern, if you will.

But, what is it actually giving us? What problem is it solving? Because if it’s not, get rid of it. Reduce it down.

Let me tell you about If you go there, you’ll find this podcast, all the past episodes and in the future, you will find the future episodes. Each episode has a page that includes audio, video, and a text transcript. You can listen, watch, or read, if that’s how you like to do it, whatever floats your boat at that time.

There are subscribe links for the podcasts so you can get the new ones as they come out. There’s also links to my email and other social media that, if you want to get in touch with me, you want to follow, you want to send me a message, say hello, say goodbye, whatever, my email is out there.

I can’t say I’ll see you later because this is a recording and you are watching me. I will say, I hope you liked this episode and that you will watch others. Thank you. Bye-bye.

The post Is your layer of indirection actually useful? appeared first on LispCast.


Hard CIDER: REPL Shortcuts

One common struggle people have with CIDER (and Emacs in general) is that there are way too many keybindings to remember, and some of them require pressing a lot of keys. While there are many ways to address those common problems in Emacs, today I’ll focus on one that’s really simple and CIDER specific.

CIDER’s REPL provides a neat trick inspired by SLIME - REPL shortcuts. They are extremely easy to use - just press , while at the REPL prompt and you’ll get a completing read allowing you run one of the CIDER commands that are bound to REPL shortcuts. Let’s now revisit tidying up our REPL in the context of REPL shortcuts, so you can get a better idea of how this actually works:

CIDER REPL Shortcuts

Note that this example uses the ivy library to display the list of commands that are available. You’ll get different visuals if you’re using some other minibuffer completion library (e.g. ido or icomplete).

If you don’t like using , as the trigger key you can change it to some other key easily. Just put this in your Emacs configuration:

(setq cider-repl-shortcut-dispatch-char ?\!)

The above example changes the trigger key to !. Be careful what you set here, as it shouldn’t be something with which a Clojure form might start. If you selected ', for instance, you’ll have a really hard time entering quoted forms in your CIDER REPL.

There are many commonly used commands that are bound to REPL shortcuts out-of-the-box. You can examine them by selecting help after pressing ,. The shortcuts functionality is user-extensible and you can easily add more commands there:

(cider-repl-add-shortcut "clear-output" #'foo-cmd)
(cider-repl-add-shortcut "clear" #'bar-cmd)
(cider-repl-add-shortcut "clear-banners" #'baz-cmd)

That’s all I have for you today. Simple, sweet and maybe even useful! Keep hacking!


Introducing Crux

We have just released Crux - an open-source bitemporal database.

What is Crux?

Crux is a document store that indexes documents for graph query. The indexes are bitemporal, meaning that you can query against valid time and transaction time, the usefulness of which is covered in our previous post "the value of bitemporality".


Crux is an 'unbundled' database - to use Martin Kleppman’s phrase - shipping as a connected set of pluggable parts. This means that users can swap out parts and contribute their own, and that Crux itself follows the Unix philosophy of each part doing one thing particularly well.

This pluggability allows Crux to scale with you as your scaling needs increase. You can start out using Crux with the transaction log being a local-disk based implementation, and then in future you could switch it out to Kafka, which offers much higher data throughput and retention guarantees.

With the open, unbundled architecture, it’s intended that Crux be extended and experimented with. The various 'parts' in Crux are described by Clojure protocols, meaning that users can get in and provide their own implementations that would either fully replace or decorate the existing ones.

How Crux Works

crux system2
Crux Architecture

Crux is schemaless, with transactions being submitted through the Crux API. The data is then sent to two event-log topics for storage: the transaction topic and the document topic.

We use two topics because whilst the transaction topic is immutable, messages in the document topic can be permanently erased, forming the basis of Crux’s ground-up strategy to provide ease of content eviction for data privacy reasons, to align with compliance regimes such as GDPR.

Using a separate topic for the content documents also allows for compaction to remove duplicates, as the message ID is a content hash of the document. From a Kafka perspective, the transaction topic uses a single Kafka partition, but it is in our roadmap to shard the document topic to potentially use multiple partitions.

The event-log that Crux uses is the golden store of data, with Crux leveraging Kafka’s infinite retention capability.

Crux Nodes will then ingest the data from the event-log and index the transactions and documents locally into a local Key/Value store such as RocksDB or LMDB, which acts as the foundation for both a local document store and a set of bitemporal indexes that Crux maintains for graph query. RocksDB and LMDB use fundamentally different data structures and therefore present a choice of performance characteristics and trade-offs.

Crux currently supports both a Java and Clojure API. See the JavaDocs.

Transacting and Querying

Crux supports an Edn Datalog format, similar to - though not the same as - Datomic’s. To get a feel of transacting to and querying against Crux, check out the query documentation and/or read Ivan Federov’s "a bitemporal tale".

Crux supports four transaction operations:

  • PUT


  • CAS


PUT will store a document whereas DELETE will delete it from a given valid time, but the data will still be stored in Crux history. Use EVICT to get rid of data permanently, either for all of history, or for a given valid time window. Use CAS to compare-and-swap, to ensure that the data in a document/entity is what you think it is before adding a new version, or else abort the transaction.

Inside of Crux we use a Worse Case Optimal Join algorithm, which enables the query engine to lazily stream out results for an arbitrary complex query with multiple join conditions and clauses. This, in combination with an external merge sort used for additional sorting, means that we avoid manifesting intermediary results in memory.


crux deployment
Crux Architecture

Crux can be deployed as a JAR file within your application, or Crux has a HTTP server that you can use. You can use Crux in a 'standalone' mode without Kafka (substituting in a local disk-based event-log), or you can deploy a cluster of Crux nodes that use Kafka.


Crux is open source so that you can see the code, commit history, warts and all. You can see the GitHub issues where design decisions are made, and you can contribute in this process. You can fork Crux and send PRs our way. We encourage developers to try out Crux and to expose and publish patterns of using it, to feedback their ideas and critique.

Crux is a product that JUXT will offer various support models for, including enterprise support and managed hosting. If you have any questions about Crux or would like to talk to us about using it, please email us or visit our Zulip.

Have a play with Crux - add the JAR to your project and scale up from there. Crux is Alpha. Please raise any issues on our GitHub.

crux logo


The Quadratic Formula

Quadratic Formula

If you have a general quadratic equation like this:

standard form

Then the quadratic formula will help you find the roots of a quadratic equation, i.e. the values of x where this equation is solved.

Simplify square roots

Factor and remove perfect squares:

(defn prime-factors
  ([n] (prime-factors 2 n))
  ([f n]
    (if (> n 1)
      (if (zero? (mod n f))
        (cons f (prime-factors f (/ n f)))
        (recur (inc f) n)))))

(defn perfect-squares [s]
  (loop [items (sort s) pairs []]
    (if (empty? items) pairs
      (if (= (first items) (second items))
        (recur (drop 2 items) (conj pairs (first items)))
        (recur (rest items) pairs)))))

(defn simplify-sqrt [sqrt]
  (let [sq (reduce * (perfect-squares (prime-factors sqrt)))]
    [sq (/ sqrt (* sq sq))]))
(defn quadratic-rational [[a b c]]
  (let [discriminant (simplify-sqrt (- (* b b) (* 4 a c)))]
    [(/ (- b) (first discriminant))
     (last discriminant) (/ (* 2 a) (first discriminant))]))

(quadratic-rational [3 24 48])

Graphing quadratic equations

Find the vertex and y-intercept

The x-coordinate of the vertex of a parabola in the form ax2 + bx + c is -b/2a. The y coordinate can then be found by plugging the resulting value into the equation. The y-intercept is (0, c).

(defn graph [[a b c]]
  (let [vert-x (/ (- b) (* 2 a))
        vert-y (+ (* a (* vert-x vert-x))
          (* b vert-x)
        y-int [0 c]]
  {:vertex [vert-x vert-y]
   :y-int y-int}))

(graph [-1 14 0])

Now we have a vector [1 2 3] containing the values of a simplified rational expression in the form (1 +- sqrt2) over 3.

(defn quadratic-roots [[a b c]]
  (let [square (Math/sqrt (- (* b b) (* 4 a c)))]
  (str "x = {"
       (/ (+ (- b) square) (* 2 a))
       ", "
       (/ (- (- b) square) (* 2 a)) "}")))

[(quadratic-roots [-7 2 9]) (/ 9 7.0)]


It never occurred to me

Years of Emacs, and I am still learning something new that improves my daily workflow. Recently I’ve had to deal with CSV files, generated by a little Clojure program which extracts data from different databases and produces some CSV files to be imported in another database through a new shiny application bought by our customer. Data integration, exactly.

While the data extraction is just a matter of SQL queries, the CSV files must match the stringent requirements of the new application. We ended up with messy CSV files, close but not that close to the expected results. Needless to say, sometimes we have to edit the files manually, and this is where Emacs once again shines.

The problem is simple: how can I list only the matches I am looking for in a CSV file, without printing the whole lines the matches belong to? I need a clean list of occurrences, nothing more, because debugging these files is already painful as it is.

I am so used to swiper-isearch and counsel-rg I tend to forget Emacs built-in occur. As per the documentation:

When NLINES is a string or when the function is called interactively with prefix argument without a number (`C-u' alone as prefix) the matching strings are collected into the *Occur* buffer by using NLINES as a replacement regexp.

So all is needed is pressing C-u M-s o (the equivalent of C-u M-x occur in my Emacs configuration), typing the string I am looking for and hitting RET. It doesn’t get easier than this.

Permalink Newsletter 326: Tip: consult the repl

Issue 326 – May 13, 2019 · Archives · Subscribe

Clojure Tip 💡

consult your repl

I’ve often been surprised by beginners in Slack or on IRC who ask questions about the behavior of code. They will say “what happens when I divide by zero, like (/ 1 0)?”

They are asking people to tell them what their repl will do. Why not just ask the repl?

This phenomenon was so common that some intrepid hackers added a bot to IRC that would evaluate Clojure forms right in the chat. That way, they could answer the question for the beginner by asking the bot.

I’ve seen similar behavior with some of the people I have taught Clojure. I now believe that a big differentiator between a beginner lisper and an intermediate lisper is that the intermediate lisper uses the repl to answer questions.

This may come from a habit of using other languages. In many languages, it is not worth the effort to write a program, with a main function, set up the project, compile it, and run it, just to answer one measly little question. But in Clojure, you probably have a repl open already, so the friction is removed.

So here’s the tip: the next time you want to know how something works, try to devise a small experiment at the repl. It could be as simple as evaluating one expression:

user=> (/ 1 0)
;;; try it yourself :)

Or more sophisticated, such as to answer the question “is map lazy?”

user=> (def l (map println (range 100)))
user=> (take 3 l)
;;; ???? try it yourself to see what happens

Expert clojurists don’t have all the answers. But they do know how to ask questions to the repl.

Follow-up 🙃

a note after last-week’s issue

My tip from last week (don’t use protocols for testing) spurred some debate on r/Clojure.

Newsletter Maintenance 🔧

Folks, I migrated the email service I use for this newsletter. You’re now reading this on the new service.

Email migrations can be tricky. I would really appreciate it if you report any issues with deliverability. That includes not receiving the email, the email showing up in spam, and any warning messages you get in your email client. I want to make sure you can get the emails you want.

The most obvious benefit to you of the new system is that I am no longer tracking links you click on. Unlike my old system, my new system can turn off link tracking. You actually know where the link will send you before you click. And nobody is tracking the clicks.

I also believe the new system has better deliverability, meaning you’re more likely to get it. I am still tracking email opens, because I would like to make sure that people are getting the email. Open tracking is the best way to assess the health of your email delivery system.

Currently Recording 🎥

I am currently recording a course called Repl-Driven Development in Clojure. There is one new lesson ready this week.

  1. Running a Clojure repl in a Java application shows 2 different ways to run a repl in a Java application. We spend a little extra time getting nRepl to work well with Cider.

I’ve been getting lots of great advice from people watching it. Thanks! This is going to be a much better course because of it. If you watch it, please let me know how it can improve.

There are already 7.75 hours of video. The recording is starting to wind down. I’ve got a couple more lessons I’d like to record. If there is something in there you would like that you don’t see, now is your chance to ask for it. It will be launching soon.

Of course, members of get the course as part of their membership for no extra cost. Just another benefit of being a member.

Check out the course. The first lesson is free.

Clojure Media 🍿

Dutch Clojure Days 2019 published its videos a couple of weeks ago. You can see them here on YouTube.

Brain skill 😎

Get active. Physical activity is known to increase oxygen flow, regulate blood sugar, and stimulate neurogenesis (new brain cell growth).

Clojure Puzzle 🤔

Last week’s puzzle

The puzzle in Issue 325 was to implement Conway’s Game of Life.

You can see the submissions here.

There are many ways to implement Game of Life, and I think they’re all instructive.

My favorite implementation is from Christophe Grand. It’s just so small. It shows that you can turn the problem inside out from how it is normally solved. All you have to do it peer hard into the heart of the problem.

When you stare at the soul of Conway’s Game of Life, you can see that it’s all about counting live neighbors. All of the rules hinge on that. You can also use the property that neighborness is symmetric. That is, if you are my neighbor, I am your neighbor.

Once you see that, the solution is so small. Here is my version of Christophe Grand’s:

(defn neighbors [[x y]]
  (for [dx [-1 0 1]
        dy [-1 0 1]
        :when (not (= 0 dx dy))]
    [(+ x dx) (+ y dy)]))

(defn step [lives]
  (set (for [[pos live-neighbors] (frequencies (mapcat neighbors lives))
             :when (or (= 3 live-neighbors) (and (contains? lives pos)
                                                 (= 2 live-neighbors)))]

This week’s puzzle

Largest integer from concatenation

If I have a list of integers, I can concatenate their base-10 strings to get one large integer. However, if I reorder them, then concatenate them, I could get a larger integer. The task here is to find the largest integer you can create by concatenating a given set of integers.

Here are some test cases:

(maxcat [1 2 3 4 5 6 7 8 9]) ;=> 987654321
(maxcat [12 34 56 199])      ;=> 563419912

As usual, please send me your implementations. I’ll share them all in next week’s issue. If you send me one, but you don’t want me to share it publicly, please let me know.

Rock on!
Eric Normand

The post Newsletter 326: Tip: consult the repl appeared first on


What a monoid is and why monoids kick monads’ butt

Everyone talks about monads but monoids are where it’s at. Monoids are simple and make distributed computation a breeze. In this episode, we learn the two properties that make an operation a monoid, and how to use it do distributed computation.


Eric Normand: What is a monoid? By the end of this episode you will understand the two properties of monoids, and you’ll be able to apply them in your systems. That’s a big promise. My name is Eric Normand and I help people thrive with functional programming.

Why are monoids important? Why even bring them up? That’s a really good question.

I know a lot of people talk about monads. I think it’s mostly because they want to know what they are and they don’t understand them. They think there’s some magic in Haskell about how IO happens with monads. That’s another topic.

This episode is about monoids. I actually think monoids are more interesting, more applicable to helping us write better code. Especially in a parallel or distributed code in the system that’s got a computation that has to spread out over different course or different machines.

A monoid lets you break up a task into smaller tasks pretty much arbitrarily. You don’t have to spend a lot of computation figuring out how to break it up. You break it up into small tasks, spread that out to different workers. These are on different threads or in different machines. Here’s the key. The monoid lets you put them back together. Put the answer back together.

What are the two properties of monoid? The two properties of a monoid…First of all, it’s an operation. A monoid is an operation. It’s not a type or a kind of type. It’s not a property of a certain class of values. It’s a property of an operation over those values. For example, addition of numbers is a monoid. Multiplication of numbers is also a monoid.

What are the two properties that operation is associative? I have a whole episode on what is associative. I’ve explained it before. I’ll explain it briefly in a second. The other property is that operation needs to have an identity value. I also have an episode on that. You should look those up if this is confusing. I’ll go over a brief recap.

An associative operation is one that has two arguments, otherwise known as a binary operation. Addition has two arguments. It has A plus B. A and B are the two arguments. That’s pretty clear. It’s got to have two arguments.

Here’s the other thing. It’s got to take values of the same type and return a value of that same type. All three things, the two arguments and the return value have the same type.

Look at numbers, takes two numbers, returns a number. That’s addition, right? Multiplication is the same way. Takes two numbers and returns a number. It’s not returning a string or something else. It’s returning a number.

That’s part of what makes it associative. The other thing is that the grouping. Because you’ve got this…I look at it like a triangle. Like an upside down triangle. Got an A and a B and it gives me a C and they’re all the same type.

Because I’ve got the A and the B at the top and the C at the point at the bottom, you can think of this associative operation as a combining operation. Addition is a way of combining two values.

It’s not always combining. It’s not always clear that that’s a good way to think about it. In multiplication, are you really combining two numbers? With addition, you definitely are. You’re combining two piles of rocks into one big pile of rocks. That’s addition. It’s where the abstract operation of addition comes from. If you just take a piles of things and you put them together and now you have a big pile.

If you imagine, I have three things to add. I have A, B and C and I want to add them up. I can add up A and B first and get a number and then I add C to that number, so I use the same operation again. I did a plus, A plus B, get a number, let’s call it D and then I take that D and I do D plus C and now I get E and that is my answer.

You notice it’s…I’m trying to make it graphical here, I add A and B and those are in a triangle and lead down to the point D. Then that D and C form a new triangle and it leads down to the point E.

That’s one way I can do it, or I could do this other way. I have A plus B plus C. I could take C as the top of the triangle, go down to D and go down to E. Graphically, it’s symmetrical. It’s the same. I’ll give the same answer but I’ll group them differently.

The triangles group on one side to the left and on the other side they group to the right. That is what associative means. Really, when you put this into the context of doing work over distributed systems, what it means is I can make a hierarchy of workers where the workers at the bottom of the hierarchy, the bottom of the tree, are doing, adding up the tiny bits.

There’s like their supervisor, right above them, adding up all the work that they’re doing, into a bigger number. Then there’s the supervisors of the supervisors, and they’re adding up all the supervisors work. There’s a supervisor of the supervisors of the supervisors and they’re adding up all their work and there’s like the super-super-supervisor, up at the top adding up everything.

You can make a hierarchy and it doesn’t matter how they’re grouped. That’s what associative means. That the triangles, as they’re added up, I guess this way the triangles are going up but as they’re added up, it doesn’t matter how they’re grouped.

I can do the work at the bottom and it will get grouped up into the top arbitrarily. It’s what you don’t want to do when you’re distributing the work is spend a lot of time thinking spending CPU power, figuring out what’s the best way to break this problem up. You just want something, like I’m just going to chop this pile of rocks and have put my hands in it and push it roughly to.

You get one half and you get one half and start counting. What they’re going to do is say, “It’s too big for me to count, I’m going to split it roughly in two and I’m going to tell my sub-supervisors to work on it.” Then they’re going to split it up into two and give it to their two workers. The answers will bubble up through this associative operation.

Probably digressed too much about associativity here. I do have a whole episode on it. The other thing is it needs an identity. This is in order to be considered a monoid. This is just the definition of a monoid.

Identity really just means an empty value. Let me put that in quotes, “empty value.” In numbers for addition, the empty value is zero. It’s like, where do you start. Before you count, where do you stand, where do you start, you start at zero and then you start one, two. You don’t have to say it when you count because it’s assumed. I have zero before I start.

With multiplication, the identity is one which is where you start, because if you started at zero you multiply it out and you’re always going to get a zero. One is like the null value, the value that doesn’t change anything, that’s what the identity means.

Basically, you need a way for someone to get started. You need a value that they can start at. Your workers are saying, “Well, I haven’t counted anything yet. I’m at zero.” Don’t want to digress too much into identity again. There’s an episode on that if you want to learn more about it.

Let me give some examples of monoids in case this wasn’t clear that it’s not just about addition. String concatenation is a monoid. If I have string A and string B, I concatenate them I get a string C. Notice the types are all the same. String, string, and string. Then it’s got an identity. What is the identity of strings of concatenation?

It’s the empty string. It’s where you start. If I take an empty string and I concatenate it on to any string I get that string back. Front or back. Likewise, with list concatenation. Lists and strings are pretty similar structurally. Map.merge() is associative. If I take two maps and I make a new map that has all the keys from A and all the keys from B, that is associative.

The identity — you can probably think of it — is the empty map, so no keys. That’s perfectly fine. I have already talked about addition and multiplication. AND and OR are associative. They take two booleans and return a boolean. The identity is different for OR, the identity is true, and for AND, the identity is false.

Is that right? No. I said that wrong. For OR the identity is false, and for AND, the identity is true. You can work that one out for yourselves on some paper. It’s a useful exercise. Some other things that you might not think of as monoids…if you squint, they’re combining operations. Min and max.

Min takes two numbers and will return the smaller of them. Again, remember, we’ve got the two arguments, same type. Return value is the same type. It doesn’t matter how you group it. If I have three numbers A, B, and C, and I want to figure out what is the smallest of all three of these, I can take the first two and min them.

The results of that and the third one, I can min it. It doesn’t matter how I start, how I group it. Same for max. Min is associative. What is its identity? The identity of min has to have the property that if I do min with that identity and any other number — let’s call it A. The identity mined with A will always give me A, so what number is that?

When you think about it, it must be positive infinity. It must be the biggest possible number of some logical representation of that, of infinity. A number that is bigger than any other number. Likewise, the identity of max is the smallest possible number which is negative infinity, some logical representation of that.

That’s great because that means in a hierarchy — in a tree — you can figure out the biggest number or the smallest number. I’ve seen this in a video somewhere. I don’t know where but someone was trying to show that you can do distributed work in people. If each person does just a very small task, the whole room could come up with the correct result of the computation.

What they did was they asked everyone to write down their birthday and then when everyone had finished that…they were in rows. It was like a theater style classroom and they said, “If you’re on the left,” so there’s no one to your left, you’re the furthest on the left, “pass your birthday to the right and then if you have two birthdays in your hand, pass the biggest one to the right.”

The highest birthday in the row starts to bubble up to the right because everyone’s doing this one little comparison operation, they passed it to the right. The person on the right, the person with no one to pass it to, they have two, what they’re supposed to do is pass the highest one forward.

Now you have the rows are bubbling up, the highest birthday to the front. The person in the front right position in the classroom when everyone had passed…everyone except for the people on the left, they now have one card, except for the person in the front who has two. You’re right, he or she has two cards.

Some card plus the highest one which is the highest birthday. Then the professor took the card and said, “This is the birthday of the oldest person in the room.” Right of the bat there and then, that person stood up and it was some old person. [laughs] It’s like, “Oh, it worked. There is the oldest person in the room.”

Then they asked, “Is anyone older than that?” It was, “No.” They found the answer. When you think about this, they were doing the max operation on the dates, or maybe the min operation because it’s the oldest birthday, so it’s the minimum birthday.

They were doing min and they were able to combine them. First on a one-on-one basis but then you notice the min…each individual person was doing one comparison but then when the min for the row was discovered, that one was mined with the whole class. All of the rows were mined together.

This is an example of arbitrary grouping, so whatever rows happened to exist, it was arbitrarily grouped and then arbitrary grouping of the rows. They were in some random arbitrary order as well. When the answer bubbled up, it was a single return value that had been the mean of multiple means.

That is the power of associativity, right there. You can start with two things, two dates, compare them. You get an answer. Then someone else is doing the same thing. They’re comparing two dates. They get an answer.

Now, you’re comparing those two. You get an answer. It’s like a hierarchy and the answer is bubbling up. I think that that’s a pretty good story for explaining how these things can do distributed work, and why they’re so good at it. You could imagine having this classroom do lots of work like that, lots of different computation tasks.

They could add up numbers, where each person has two numbers, and then they pass the answer to the end of the row, and they add up all these numbers, etc. Then the rows add up all their numbers, and the work is done faster, because it’s done in parallel. All the rows are working at the same time, instead of one person going through one number at a time, adding, and adding, and adding.

I just wanted to give one more example of a kind of task you’d do. Let’s say that you’re at a school, it’s just one building, it’s got all these classrooms, and you need to figure out a lunch order for everyone in the whole school, because you’re going to have lunch delivered today.

You know that if you went individually, as one person, to each person in the school, you could figure out the lunch order, but it would take longer than you have. It’s already 9:00 AM, you have to get everybody’s order, then it has to have time to cook it, and deliver it for lunch, so there’s just no time to walk to every person and ask them.

What can you do? To break this problem up, you can say, “Well, if I have two lunch orders, I know how to combine them into a bigger lunch order.” If everyone writes down what they want, let’s say their student ID and then what they want for lunch. Everyone has that first initial piece of data.

You can have, let’s say, someone at the end of the row in their classrooms combine all of those into a single, bigger order. That’s a combining operation. Then someone at the front of the class would combine all of the rows together into a single one.

They would run to the floor manager. That floor manager would aggregate all of the classrooms on that floor together into a bigger lunch order. Then all of the people, all of the floors move even down or up. It doesn’t matter, but let’s say they all send their order down.

There’s a school manager that adds up all of those orders into one big order. Then they send it off to the catering service.

What you’re doing is a HashMap merge. Everybody’s got a key and value. It’s their student ID and then their lunch order. You’re doing a merge between, first, everyone in the row. Maybe, as it move to the right, it gets aggregated up. Then, as it moves forward in the classroom, the rows get aggregated up into a classroom HashMap.

The classroom HashMaps get merged together into a floor level HashMap. Then the floors get merged, down onto the bottom floor, into one big school-wide HashMap.

That can happen very quickly, because each of the floors is up, and each of the classrooms, and each of the people, and in classrooms each row, each person is working in parallel. It’s basically the amount of time it takes each…

The school-wide lunch order is the answer. It just takes as long as, let’s say, it’s four floors. The merging of four hash maps, plus, however long one floor takes to generate the answer because the other three floors are happening in parallel. It’s a way of gaining some time by doing things in parallel.

It’s the same thing. You’re taking this problem. You’re breaking it up. You’re making sure that the types are the same on the two arguments and the return value. The type is hash map of ID to order. Now you can merge them. You’ve turned it into a monoid.

You could say if you were going to do this in an imperative, just loop through all the students’ way. You might never make the hash map for each individual student because you might just say, “What’s your student ID and what’s your order? What’s your student ID? What’s your order? What’s your student ID? What’s your order?”

You never made that smaller type or the smaller hash map to make it of the same type, but by giving everyone the instruction to make the small hash map, you’ve now turned that into a monoid, which allows for this distributed collection of an aggregation of the data into a single answer.

You’re breaking up the problem, the big problem into a lot of smaller problems. You’re having the other computers do the small problems, and then you’re combining the answers. I’ll recap super quick. A monoid is an operation, like hash map merge, that is associative and has an identity.

Some examples, string concatenation, min, max, map merge, addition and multiplication. There’s a bunch of others. I went over a story about how a room of people could calculate the oldest person in the room in a distributed way.

I also talked about how you could calculate the lunch order of a really big school in less time than it would take to go to each person individually. That was it. I have a little takeaway that you could do. The way I like to think of it is this classroom.

It’s like this classroom that did this distributed calculation is actually a pretty good way of thinking of how you can distribute some work. If you could come up with this operation that each individual person can do, and then the rows can do, and then the whole room can do, if it’s the person on the right-hand side of the row can combine, then you’ve got a monoid.

You can distribute the work to the whole classroom.

Think of how you would get classrooms to do the work. Some examples that you might think of is adding, adding numbers together. Let’s say, you wanted to calculate the…Everyone has like some money in their pocket or some are in their purse or something. You could figure out how much money is in the room right now.

That’s pretty cool. You can add it all up. You figure out the youngest person, the oldest person, you can figure out this lunch order or some other operation like that that is able to combine or figure out maybe.

Think of it like a tennis tournament. You’re trying to figure out who the best person is. You take two people and you figure out the winner between those two. That winner becomes the return value.

As a hierarchy, you can figure out who is the best tennis player in this tournament. You don’t have to play everybody against everybody. You’re doing like a monadic operation to get up to the top. I do want to tell you, if you want to see all the past episodes and if in the future you want to see future episodes, they don’t exist yet. That’s why they’re future. You can go to There you’ll find links to subscribe.

You’ll also find a page for each episode that includes the audio version, a video version, and a text transcript of what I’m saying here. You’ll also find my email address and links to social media if you want to get in touch with me.

I’d love to hear about the kinds of problems that you’ve found that you can solve in this classroom. Let me know about them. We’ll talk about it. We’ll get into a nice, deep discussion.

I can’t say I’ll see you next time, so until next time. I’m Eric Normand. See you later.

The post What a monoid is and why monoids kick monads’ butt appeared first on LispCast.


Copyright © 2009, Planet Clojure. No rights reserved.
Planet Clojure is maintained by Baishamapayan Ghose.
Clojure and the Clojure logo are Copyright © 2008-2009, Rich Hickey.
Theme by Brajeshwar.