~ / track A / clojure basics

Namespaces and requires

Basic

A namespace is Clojure's unit of modularity. It groups vars (defined with def, defn, …) under a common qualified name and controls how other namespaces see them. The file src/myapp/core.clj is expected to begin with (ns myapp.core ...) — file path and namespace name are mirrored, with - in filenames becoming _ on disk (myapp-utilsmyapp_utils.clj).

The ns form

(ns myapp.users
  (:require [clojure.string :as str]
            [clojure.set :refer [union]]
            [myapp.db :as db])
  (:import [java.time Instant LocalDate]))

(defn touch [u]
  (assoc u :seen-at (Instant/now)))

Three blocks inside ns:

  • :require pulls in Clojure namespaces.
  • :import pulls in Java classes.
  • (Less common today: :use, :refer-clojure, :gen-class.)

Inside :require, three modes:

FormEffectWhen
[clojure.string :as str]Alias only; access via str/joinDefault — keeps origin visible
[clojure.set :refer [union difference]]Pull specific names into your nsHeavily-used operators or is, deftest in tests
[my.lib :refer :all]Pull everythingAlmost never — pollutes your ns

The pragmatic default is :as aliases. :refer is reserved for short, canonical names that read better unqualified (<!, >!, is, testing).

Qualified vs unqualified

Every var has a fully-qualified name:

clojure.string/join     ;; full name
str/join                ;; via the alias above
join                    ;; only if you `:refer`red it

This matters because keywords can also be qualified::user/id expands to :myapp.users/id in the current namespace. Qualified keywords are essential for clojure.spec, Datomic, and any cross-namespace map contract.

Loading and reloading at the REPL

Inside a running REPL you don't write ns forms — you require:

(require '[myapp.users :as users])
(require '[myapp.users :as users] :reload)     ;; pick up file edits
(require '[myapp.users :as users] :reload-all) ;; reload transitive deps

Editors send these for you when you save a file. The reloaded namespace replaces the previous one — any def you removed from source still lingers in the live ns until you restart or use tools.namespace's refresh.

File-to-namespace mapping

NamespaceFile on classpath
myapp.coremyapp/core.clj
myapp.users.dbmyapp/users/db.clj
my-app.coremy_app/core.clj (dashes → underscores)

The classpath is set by [[tools-deps]] / Leiningen; the namespace declaration inside the file must match the path.

ClojureScript and .cljc

  • .clj → Clojure (JVM) only.
  • .cljs → ClojureScript only.
  • .cljc → shared. Uses reader conditionals: #?(:clj ... :cljs ...).

Most modern libraries publish their public API as .cljc and dispatch host-specific code internally.

Try it

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

Common pitfalls

  • Circular requires. A requires B requires A. Restructure or extract a third namespace with shared definitions.
  • Side-effects at the top level. A (def conn (db/connect ...)) at ns load time will fire whenever the namespace is required. Use [[component-systems]] for anything stateful.
  • use instead of require + :refer. use is legacy; everything you bring in becomes a name in your ns, and you lose the origin. Prefer explicit :refer [...] lists.
  • Forgetting :as. (:require [very.long.namespace.path]) requires you to type very.long.namespace.path/foo every time. Alias it.

Real-world

PatternWhere
One namespace per "thing" (entity, route, service)Web apps, CRUD systems
core ns as the public API; helpers in impl/*Libraries (e.g. clojure.core.async)
.cljc for shared schema/utility codeFull-stack apps (Clojure backend + CLJS frontend)
Qualified keywords per nsDatomic, [[malli]], [[clojure-spec]] schemas
tools.namespace/refreshReloaded workflow in long-running dev REPLs

Check yourself

? quiz

You require `[clojure.string :as str]`. Which name reaches `clojure.string/upper-case`?

Exercise

Create two namespaces:

  1. myapp.math exporting square and cube.
  2. myapp.main that requires myapp.math with alias m and uses both functions.

Then sketch the same project with the math functions split into myapp.math.basic and myapp.math.advanced. Decide: should myapp.main require both, or should myapp.math re-export them from a single facade namespace?

 status: new