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.