/img/clojure-banner.png

Reader Conditionals in Clojure Commons

Defining different behavior based on if the invoking function is Clojure or ClojureScript

Written by: Alex Root-Roatch | Wednesday, October 16, 2024

Clojure Commons

One of the coolest things about Clojure is the ability to operate as both a powerful backend language on the JVM and to compile down to JavaScript on the frontend for building websites and React applications. This allows us as developers to use one programming language across the entire stack without the limitations imposed by running Node.js as the backend language.

This is made even easier with Clojure Commons, which are files that end in a .cljc extension. These files can be used by namespaces on the backend or the frontend, allowing for more reusable code structure when functions may be needed for both. For example, the minimax functions are needed in the CLJ namespaces of my Tic-Tac-Toe dekktop app, but are also required in the CLJS namespaces for the React version of the app.

Sometimes, though, you may need to tweak the behavior of a function slightly when it runs in ClojureScript, perhaps to add React keys to elements in a sequence. This is where reader conditionals come in handy. There are two types: standard and splicing

Standard Reader Conditionals

Standard reader conditionals behave like a cond block, using the keywords :clj or :cljs followed by the code to be executed. The syntax is a bit simpler though; it simply consists of a pound sign and a question mark #? followed by parenthesis.

(defn with-keys [coll]
  #?(:clj  coll
     :cljs (util/with-react-keys coll)))

This example will simply return the collection if it is running on the backend or add React keys to every element in the collection if it's running in the browser. The util namespace that with-react-keys comes from is also conditionally required using a reader conditional to avoid trying to require a CLJS namespace in CLJ, which would cause a compiler error.

The keyword :default can also be provided as a fallback should the function be used in an environment that is neither CLJ nor CLJCS.

Another good use case for standard reader conditionals is when interop is needed. When running in CLJ, Clojure interops with Java, but ClojureScript interops with JavaScript.

Splicing Reader Conditionals

Splicing conditionals are used for splicing a list into their containing form. Their syntax simply adds an at sign @ after the question mark ?.

(defn build-list [] 
      (list #?@(:clj  [5 6 7 8]
                :cljs [1 2 3 4])))

; CLJ equivalent
(defn build-list []
      (list 5 6 7 8))

;CLJS equivalent
(defn build-list []
      (list 1 2 3 4))

Notice that the individual numbers in the list are passed as separate arguments rather than the list itself. This is the important difference between splicing conditionals and standard conditionals. A standard conditional would result in this:

(defn build-list [] 
      (list #?(:clj  [5 6 7 8]
               :cljs [1 2 3 4])))

; CLJ equivalent
(defn build-list []
      (list [5 6 7 8]))

;CLJS equivalent
(defn build-list []
      (list [1 2 3 4]))

Conclusion

Clojure Commons makes reusing code between the frontend and backend a breeze. Reader conditionals allow us to fine tune our CLJC functions and namespaces even more to account for the idiosyncrasies between the JVM environment and the browser. For most cases, the standard reader conditional is sufficient, but in some cases where multiple values may need to be passed in at once, splicing conditionals will prove very useful.

Explore more articles

Browse All Posts