Is it possible to split a Clojure namespace over multiple source files when doing ahead-of-time compilation with :gen-class
? How do (:main true)
and (defn- ...)
come into play?

- 31,584
- 38
- 145
- 282
1 Answers
Overview
Certainly you can, in fact clojure.core
namespace itself is split up this way and provides a good model which you can follow by looking in src/clj/clojure
:
core.clj
core_deftype.clj
core_print.clj
core_proxy.clj
..etc..
All these files participate to build up the single clojure.core
namespace.
Primary File
One of these is the primary file, named to match the namespace name so that it will be found when someone mentions it in a :use
or :require
. In this case the main file is clojure/core.clj
, and it starts with an ns
form. This is where you should put all your namespace configuration, regardless of which of your other files may need them. This normally includes :gen-class
as well, so something like:
(ns my.lib.of.excellence
(:use [clojure.java.io :as io :only [reader]])
(:gen-class :main true))
Then at appropriate places in your primary file (most commonly all at the end) use load
to bring in your helper files. In clojure.core
it looks like this:
(load "core_proxy")
(load "core_print")
(load "genclass")
(load "core_deftype")
(load "core/protocols")
(load "gvec")
Note that you don't need the current directory as a prefix, nor do you need the .clj
suffix.
Helper files
Each of the helper files should start by declaring which namespace they're helping, but should do so using the in-ns
function. So for the example namespace above, the helper files would all start with:
(in-ns 'my.lib.of.excellence)
That's all it takes.
gen-class
Because all these files are building a single namespace, each function you define can be in any of the primary or helper files. This of course means you can define your gen-class
functions in any file you'd like:
(defn -main [& args]
...)
Note that Clojure's normal order-of-definition rules still apply for all functions, so you need to make sure that whatever file defines a function is loaded before you try to use that function.
Private Vars
You also asked about the (defn- foo ...)
form which defines a namespace-private function. Functions defined like this as well as other :private
vars are visible from within the namespace where they're defined, so the primary and all helper files will have access to private vars defined in any of the files loaded so far.
-
3Very nice, complete answer! BTW, I'm almost finished on my first pass through _The Joy of Clojure_. Great book! – Ralph Jan 14 '11 at 14:38
-
Thanks for sharing this answer. Is it considered a good practice still, 2 years later? (I know things change fast.) I see that Clojure itself still uses this technique. – David J. Jan 03 '13 at 17:12
-
9As of today this is still best practice if you are sure you want multiple files to generate a single namespace. However, that itself may be less common now than it was. An alternative may be to define all public vars of your ns in a single file, and move all helper vars and functions to a separate "implementation" namespace. The vars in impl would technically be public, but a ns docstring indicating they are not part of the documented API is common and should be sufficient. – Chouser Mar 26 '13 at 15:04
-
1Do we know if any common Clojure tooling has issues understanding multi-file namespaces? Lein? Boot? Cider? nREPL? Kibit? Eastwood? Cloverage? Etc... – Didier A. Jan 03 '18 at 21:44