Daniel Compton

The personal blog of Daniel Compton - Projects

Requiring records in Clojure and ClojureScript

One of the datatypes Clojure gives you is the Record. It is used in both Clojure and ClojureScript, but due to implementation differences, the syntax to use records from one namespace in another is different.

Clojure

Clojure generates a Java class for every defrecord that you create. You need to :import them, just like you would a standard Java class. Lets say we have a small music store app that sells vinyl:

(ns myapp.cool-music)

(defrecord Vinyl [speed])

;;; meanwhile ...

(ns myapp.music-store
  (:import [myapp.cool_music Vinyl]))

(instance? Vinyl v)

;; or

(ns myapp.music-store
  (:import [myapp.cool_music.Vinyl]))

(instance? myapp.cool_music.Vinyl v)

There’s a few things to note here:

  1. Because Clojure is generating Java classes, they follow the naming conventions of Java classes, where dashes get converted to underscores.
  2. You should probably prefer the first example where the record is aliased, over the second one where the record is fully qualified.
  3. Clojure dynamically generates the class for the record when that namespace is required (ignoring AOT compiling). If your code never requires the namespace that the record lives in, then it won’t be created. This will cause ClassNotFoundException errors when you try to import the class. In our trivial example, that would mean changing the ns import to:

    (ns myapp.music-store
      (:require [myapp.cool-music]
      (:import [myapp.cool_music Vinyl]))
    

ClojureScript

ClojureScript generates the code for a record in the namespace it is defined in. You can require, refer, and alias a record just like you would any other function in a ClojureScript namespace.

(ns myapp.cool-music)

(defrecord Vinyl [speed])

;;; meanwhile ...

(ns myapp.music-store
  (:require [myapp.cool-music :refer [Vinyl]))

(instance? Vinyl v)

;; or

(ns myapp.music-store
  (:require [myapp.cool-music :as m))

(instance? m/Vinyl v)

;; or

(ns myapp.music-store
  (:require [myapp.cool-music))

(instance? myapp.cool-music/Vinyl v)

There are no hidden gotchas with requiring records in ClojureScript. At one point ClojureScript supported importing records like Clojure, but this was backed out later.

Clojure and ClojureScript

If you want to work with Clojure and ClojureScript (and let’s face it, who doesn’t?) then you can use Reader Conditionals to require a record in both language variants at the same time.

(ns myapp.music-store
  (:require #?(:clj [myapp.cool-music]
               :cljs [myapp.cool-music :refer [Vinyl]]))
  #?(:clj
     (:import [myapp.cool_music Vinyl])))

(instance? Vinyl v)