Setting up Clojure and SCIMUtils as an absolute beginner
Clojure setup
So recently I was starting up on Clojure for SCIMUtils library, which is an excellent port of original scimutils written in MIT-Scheme. Clojure version provides overall better documentation and tooling experience than the Scheme version.
However Clojure is bit more eccentric for my taste and I was having hard time getting started. Shout out to user pmonks from the Discord Clojure chatroom Discljord
for being patient and helpful enough to guide me though its first steps. Following is the conversation with him. Most of it is replicated verbatim, except few changes in structure, and editing where required. Plus it is really really hard to get help on MIT-Scheme
Bit of background
So the first thing that’s a bit odd is that you would normally use either lein
or clj
, but rarely both together. They’re basically competing build tools / environments.
Sort of - it’s supposed to be a more modern take on a Clojure build tool, and is published by Cognitect (the creators of Clojure), so has more “cachet”. Whether it’s “better” than Leiningen or not at this point is a matter of debate. clj
just wraps Clojure in rlwrap
- they are otherwise identical. So yes, for interactive REPLs, you’d usually want clj
.
Structure of Clojure project
Anyway, one of the first things to understand about package management / libraries in Clojure (which is actually a limitation of the JVM) is that hot-loading libraries is a bit fraught. There are hacky ways to partially do it, but I would not recommend them unless you’re already fairly familiar with how the JVM loads code. So what we have to do instead is declare our dependencies up front, then start a REPL (and if the dependencies change, generally speaking the best bet is to quit and restart the REPL).
For clj & clojure, dependencies are expressed in a file that (by default) is called deps.edn
. To declare a dependency on SICMUtils, the contents of that file would be
{:deps {sicmutils/sicmutils {:mvn/version "0.22.0"}}}
This is same as given on Clojars page clj column. Clojars “knows” about the clj/clojure tools, so they provide this as a handy copypasta.
Also the clj tool assumes source code is housed under a sub directory called src
. So the directory structure should be:
new_project/
|
+- deps.edn
|
+- src/
|
+- core.clj
Though if your namespace is new-project.core, that should actually be:
new_project/
|
+- deps.edn
|
+- src/
|
+- new_project/
|
+- core.clj
The core.clj
contains simple hello world program.
(ns new_project.core)
(defn hello
[]
(println "Hello World"))
Once you have that file in an otherwise empty directory, if you start a clj / clojure REPL in that directory, you should see it download the library (first time only) then start a REPL where that library is available.
All downloaded packages are by default kept in a “per user” cache in ~/.m2.
That means that if you use the same dependency in multiple separate projects you’re not downloading the same libraries over and over again. This is standard practice across many/most JVM-hosted languages.
Clojure simply leverages the underlying JVM ecosystem.
Running the Program
To load code from a file you would normally require that file’s namespace, then use the vars it declares from your own namespace (user in the case of the REPL).
Now to load code from a file using require, there is a naming convention for that file’s name that you have to follow (since Clojure has a particular mapping from ns symbol to file-on-disk).
As an example, if your file declares a namespace called my.cool.namespace, the file would need to be called ./my/cool/namespace.clj (yes that’s a directory structure).
Oh and one JVM oddity to watch out for - if your namespace name includes hyphens anywhere (“-“), those would be replaced with an underscore (“_”) in the filename. This is another JVM oddity that Clojure has to workaround.
Once you have your core.clj named and located properly, to load and use it in the REPL, you’d just require the namespace e.g. in above case it will be
(require '[new_project.core as cr]).
vars in that namespace are then available via the cr
prefix. e.g. (cr/hello)
Oh and one nice thing that Clojure can do for Clojure code, is that if your file changes you can reload it without exiting the REPL. To do that:
(require '[new_project.core as cr] :reload-all)
So from inside your poject folder you should have final structure as:
$ tree .
.
├── deps.edn
└── src
└── new_project
└── core.clj
2 directories, 2 files
And the clj
commandline looks like:
$ clj
Clojure 1.11.1
user=> (require '[new_project.core :as cr])
nil
user=> (cr/hello)
Hello World
nil
user=>
Thats it! Now you can head to SCIMutils docs and start doing what the page tells you to.