It's been a year since
the PeepCode
screencast on Clojure (now distributed under PluralSight)
was released. While it's aged surprisingly well given the
relative youth of the Clojure language (1.0 hadn't even been
released at the time), there are a few things that could use
some updates. I thought it would be helpful for me to step
through Mire,
the sample project that's built up in the screencast, and update
it to reflect the changes that have since occurred in the
Clojure ecosystem.

Probably the most obvious thing about the Mire project as it's
seen in the screencast that shows its age is its ad-hoc
build. (Step 12 of the screencast) At the time there weren't any
good ways to build and distribute Clojure projects, so Mire simply
contained a copy of Clojure and Contrib in its git repository and
included a shell script to perform compilation and
packaging. Apart from being just generally tacky, this actually
caused the repository to bloat up by 16MB due to the fact that git
is really lousy at storing binary files.

Kids these days have it so
easy—Leiningen
is generally used for managing Clojure projects now. I'm not going
to go into detail about this here since it's covered well
elsewhere, namely in the readme
and tutorial
as well as
the Leiningen
presentation at the Clojure Conj.
There are other alternatives, but this is certainly the most
straightforward. Leiningen gives you a basic skeleton to work from
(lein new), handles dependencies specified in your
project file, (lein deps) and creates jar files for
you, (lein jar) among other things. In the latest
version of leiningen, the lein run task can be used to
launch the server.

The main thing that's going on here is replacing a
non-toplevel def with a call
to alter-var-root. It's never a good idea to
call def from within a function. I tried to justify it by
the fact that in this case it was a function that was only meant
to be called at startup time as the docstring emphasized, (to
initialize the rooms map) but it still felt wrong.

The problem is that the rooms map must be loaded from a bunch of
files on disk, but the directory to load from isn't known until
the -main function is called. So some mutability is
called for here, but it's not really enough to justify a ref or an
atom since once the server starts it will never change. In the
updated version, alter-var-root replaces
the def. It takes a var (mire.rooms/rooms in
this case) along with a function to apply to the current value of
the var and uses the return value as the new root value of the
var. It's also possible to simulate change to a var
using binding, but this only affects the current thread,
and in this case we want the changes to be available to all
threads.

Justifiable use of alter-var-root is rare, but
startup-time mutability is one of those places it makes sense. For
a more idiomatic use of alter-var-root
see Robert
Hooke
or Radagast.

Update: In retrospect, a ref really is suitable here,
since there's no reason the player's actions couldn't shift rooms
around in some way. So mutability could happen at runtime. Since
the mutability needs to be coordinated among players, a ref should
be used rather than an
atom. See commit
329056.

This one is pretty basic; it's mostly just adjusting to the new
layout of Clojure Contrib. A number of namespaces got
moved: clojure.contrib.duck-streams
became clojure.contrib.io, clojure.contrib.str-utils
became clojure.contrib.string, etc. In this case we
switched from calling clojure.contrib.str-utils/str-join
to clojure.contrib.string/join. Some of these namespaces
(most of io and some of seq) will be promoted out
of Contrib and into Clojure itself before the final 1.2.0
release.

Update: Following the release of Clojure 1.2 I replaced
most of the contrib usage with the libraries that were promoted to
Clojure. The only remaining use of contrib is the server-sockets
library.

The other thing worth noting here is that in the original version
of Mire there were a lot of unqualified calls to use,
which bring in all the vars from the specified
namespace. It's a lot more idiomatic now to either switch
to require with :as to alias the namespace to a
short name or to stick with use but to limit the list of
vars using the :only qualifier to avoid pulling every
single thing in, which is the approach taken here. This may seem
like a bit of up-front busywork, but makes it easier to track down
dependencies between namespaces and fix them in cases like the
Contrib upgrade where things get switched around.

Here we see more careful use usage along with moving the
room data files from data/rooms/
to resources/rooms/ following Leiningen conventions. The
resources dir is meant for files that aren't code but are still
used by the project, like HTML templates or data files like the
rooms that Mire uses. They will get included in the jar file when
the project is packaged.

In the Clojure 1.1 release the test-is library got
promoted from Contrib into Clojure itself, so that's reflected
here. We also move them to a separate test directory to
reflect Leiningen convention. The tests for mire.rooms
now uses the use-fixtures function, which is a great way
to abstract out common setup to be shared among tests.

While OOP test frameworks use setup/teardown methods,
the use-fixtures feature of clojure.test takes
advantage of the fact that tests themselves are functions. A
fixture is simply a function that takes a function argument. In
our case the fixture just runs the function inside some bindings,
but other common uses of fixtures are to create data on disk in a
try/finally block and clean up when it finishes or to
conditionally run the tests only if a given network service is
accessible. There's a lot of flexibility with clojure.test
fixtures.

What Isn't Here

There have been a lot more new features introduced to Clojure
since Mire was released. We haven't
covered transients, protocols,
deftype, or
the thrush
combinators mostly because these aren't introductory-level
topics, but also because they're the trendy new exciting topics
and have been covered well elsewhere. Update: two other
potentially confusing topics that have gotten a thorough blog
treatment since this was written
are I/O
courtesy of Isaac Hodes
and the
ns clause courtesy of Colin Jones. I hope this has been
enough to modernize the Mire project and help extend the relevance
of the screencast and associated codebase. Thanks for tuning
in!

Update: the
latest commit updates the codebase for Clojure 1.4.0 by
removing the last vestige of the old monolithic contrib.