So it looks like Clojure is finally about to get mileage at work. Which is excellent, as far as I'm concerned, because I really like the language and have been looking for an excuse or two to get more familiar with it. Unfortunately, I've had precious little programming time in my off-hours lately. Various reasons: books and papers I need to read, books and chapters I need to write, and some unrelated personal stuff. But if it's for work, rather than my own playing around, I can justify some work hours, as well as bumping it to the top of my personal studying list.

Setting up an environment turns out to be fairly simple. About the same complexity level as setting up for Common Lisp. On the one hand, the build tool gets you a copy of the language runtime so you don't need to worry about it, on the other, there's no equivalent to quicklisp-slime-helper. Anyway, the steps are

install Emacs 24 or higher. For me, this was already done. If I were doing it from scratch right now, I'd do it with nix-env -i emacs.

install leiningen. This was technically done, but I had a ridiculously out-of-date version of lein, so I ended up uninstalling that and doing nix-env -i leiningen

add cider-nrepl to your profiles.clj file. Once you've got leiningen, you should also have a file at ~/.lein/profiles.clj. If you don't, just create it (I had to). Then add

I'm not sure the tools.nrepl line is strictly necessary, but figured I might as well (Despite this, I still get WARNING: CIDER requires nREPL 0.2.7 (or newer) to work properly at the top of every REPL I start up. So, I dunno, maybe I put that in the wrong place).

install the clojure-mode and cider Emacs addons. This one caused a headache or two. I had already done some clojuring in the past, so I had an older version of clojure-mode installed, which was incompatible with cider. In the end, I had to tear it out and install a newer version. Assuming you don't have the same problem, you can just M-x package-install clojure-mode followed by M-x package-install cider.

And that's basically it. At that point, you can run cider-jack-in to get a SLIME-like interactive REPL running|1|. They're not exactly the same, notably the debugger and stack-trace in ciderisn't nearly as interactive or useful as the one in SLIME, but you still get minibuffer argument hints and a macroexpander. If it weren't for the fact that lein repl takes something on the order of 5 seconds to start up, switching over from CL would be completely painless|2|.

I mentioned a little while ago that I'm thinking about full-history data-stores. I've already kind of implemented one, though it is tightly bound to a particular data-structure, and I've done a bit of experimenting with a generalization. Having set up Clojure, I was inspired to do a bit more playing around.

An application function: a function that takes a state and an event, and returns the result of applying that event to that state

A zero: the starting state of this particular archive

Optionally, a history: the sequence of events that lead from its zero to its current state|3|

Optionally, a file and some streams: places that we'll write any additional events we get|4|

You make a new Archive by initializing some of the above points. You add an event to it by committing the event, then calling the application function on the Archives' current state. Load an Archive by opening a file, reading the Zero from it, then reduceing the remainder of the records over said Zero with the application function. And that's that. If you wanted to see a previous state of the Archive, you'd just stop folding before you got to the last event.

The above implementation is a bit simpler than my earlier CL-based equivalent, in that it doesn't yet deal with partial loads, timestamps or reconciliation. All of those look like they'll be trivial changes, and they won't impact the existing machinery at all|5|. The minimal is still fairly useful though. Here's an example use:

This is a pretty stupid example, all things considered, but it illustrates how you'd go about putting together a minimally functional, history-aware data-structure. In reality, you'd declare your table to be an atom or agent, and declare a change function that updates its state with new-event. This is the main use-case I'm considering, so it might be prudent to just make that the default behavior of the library.

First impressions are really, really good. As I've said before, I like Clojure. My impression of it is that it takes takes the best parts of Scheme and Common Lisp and runs with them. lein comes with basically all the stuff I like out of asdf, quicklisp and quickproject, with the added perks of good documentationand built-in consideration for unit testing. The only complaint I have is the annoying startup delay whenever I do lein something. In particular, the lein test command takes long enough that I'm not sure I'd want to pipe it through entr keyed on .clj file changes. I'll let you know how it goes once I've done some real work with it.

1 - |back| - If you run that inside a Clojure project directory, the REPL will also automatically load said project and enter its namespace.

2 - |back| - Incidentally, I thought this was to do with my Free Software bent, because the OpenJDK has the reputation of being slower than the Oracle equivalent. I asked a friend who uses the non-Free version, and he confirmed that the REPL just plain takes a while to start up. Not sure how to feel about that; M-x slime sets up an interactive REPL in about a second at the outside, and gives feedback on progress in the meanwhile.

3 - |back| - Optional because we may not need the full history in memory. Given my experiments with cl-notebook, I currently believe that you don't really need in-memory history unless you want to do real-time history manipulation or traversal. So, you do want it sometimes, but it potentially saves a lot of memory if you can do without.

4 - |back| - Again, optional because you might only want the in-memory representation without worrying about persisting it. I haven't come across this situation yet, but it might exist. It goes almost without saying that you want at least one of in-memory-history/tracking-file/tracking-streams, because if you have none, you don't really have a history-aware data structure. But depending on your use-case, you may need only one.

5 - |back| - Though they may require some changes to the storage format, which is why I haven't published this little library quite yet.