Use Clojure macro to generate random ID unique to each build

Clojure macros are evaluated at compile time, this enables us to do some interesting tricks. This demo shows how to generate a unique ID for each build.

First let's creat a Clojure project, assuming you've installed clojure:

clojure -M:project/new app macrodemo/playground

this will create a project in the folder playground.

Edit src/macrodemo/playground.clj, we define a myid which simply is a random UUID as string:

(def myid (str (UUID/randomUUID)))

and define myid-by-macro which is a similiar version but with macro,

(defmacro macro-random [] (str (UUID/randomUUID)))
(def myid-by-macro (macro-random))

Let's edit -main to print out the two values twice:

(defn -main
  [& _]
  (dotimes [_ 2]
    (println "==========================================================")
    (println "myid:" myid)
    (println "myid-by-macro:" myid-by-macro)))

Save and use the following command to create a uberjar file which is basically a standalone executable jar file you can run with Java:

clj -X:uberjar

Run the program with java -jar playground.jar you'll see the UUIDs printed in console similiar as follows

Note the exact UUID values printed on you machine will be different.

==========================================================
myid: 47b48885-56ef-4547-b015-d85721491820
myid-by-macro: 87e2f764-d28e-4981-94b8-ad3f05a6605a
==========================================================
myid: 47b48885-56ef-4547-b015-d85721491820
myid-by-macro: 87e2f764-d28e-4981-94b8-ad3f05a6605a

First you'll notice both versions keep the values unchanged during one execution as we'd expected.

Let's run the program again with java -jar playground.jar, you'll notice that the value of myid has changed but the macro version myid-by-macro remains the same:

==========================================================
myid: ad6fd1a0-71c4-4e47-8e13-fb82fcfcb224
myid-by-macro: 87e2f764-d28e-4981-94b8-ad3f05a6605a
==========================================================
myid: ad6fd1a0-71c4-4e47-8e13-fb82fcfcb224
myid-by-macro: 87e2f764-d28e-4981-94b8-ad3f05a6605a

The reason is that macro code are executed at comilation time, so after compilation this line

(def myid-by-macro (macro-random))

is equivalent to

(def myid-by-macro "87e2f764-d28e-4981-94b8-ad3f05a6605a")

You can even find the string value of myid-by-macro hardcoded in the compiled bytecode, try to unzip playground.jar and you'll find it inside a file named playground__init.class.

And here is the complete source code:

(ns macrodemo.playground
  (:gen-class) 
  (:import
    java.util.UUID))

;; this value changes everytime you run your program
(def myid (str (UUID/randomUUID)))

(defmacro macro-random [] (str (UUID/randomUUID)))

;; this value is fixed for each compilation
(def myid-by-macro (macro-random))

(defn -main
  [& _]
  (dotimes [_ 2]
    (println "myid:" myid)
    (println "myid-by-macro:" myid-by-macro)))
Comment