Enums in Clojure

Our enum-like type ServerState has an underlying int type. In Clojure, we’ll use the keyword type to represent the different states a server can be in.

The possible values for ServerState are defined as constants:

(def ServerState
  {:StateIdle       :idle
   :StateConnected  :connected
   :StateError      :error
   :StateRetrying   :retrying})

Implementing a Stringer-like function in Clojure can be done using a simple map lookup:

(def state-names
  {(:StateIdle ServerState)       "idle"
   (:StateConnected ServerState)  "connected"
   (:StateError ServerState)      "error"
   (:StateRetrying ServerState)   "retrying"})

(defn state-name [state]
  (get state-names state))

Next, our main function will emulate a state transition for a server; it takes the existing state and returns a new state:

(defn transition [state]
  (case state
    (:StateIdle ServerState)       (:StateConnected ServerState)
    (:StateConnected ServerState)  (:StateIdle ServerState)
    (:StateRetrying ServerState)   (:StateIdle ServerState)
    (:StateError ServerState)      (:StateError ServerState)
    (throw (Exception. (str "unknown state: " (state-name state))))))

Finally, to see our state transitions in action, we can define a main function:

(defn -main []
  (let [ns (transition (:StateIdle ServerState))]
    (println (state-name ns))
    (let [ns2 (transition ns)]
      (println (state-name ns2)))))

You can run the program using the following command:

$ clj -M -m your-namespace

Here’s what the output would look like:

connected
idle

In summary, this example demonstrates how to create and use enum-like constructs in Clojure by leveraging keywords and maps. Now that we understand how to create state transitions, let’s explore more features of the language.