Created
October 23, 2018 00:55
-
-
Save exupero/e71a2ddcf99686cfc3c32b0f249e5ada to your computer and use it in GitHub Desktop.
A state machine example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Constructing a physical building is, ideally, a linear process, but in reality there can be twists | |
; and turns and backtracking to previous steps. Here's a state machine that models a simple, | |
; hypothetical construction process: | |
(def construction-process | |
{:concept {:revised-concept :concept | |
:township-approved :township-permission | |
:township-rejected :terminated} | |
:township-permission {:blueprints-delivered :blueprints} | |
:blueprints {:code-violations :blueprints-in-revision | |
:code-approval :ready-for-construction} | |
:blueprints-in-revision {:blueprints-delivered :blueprints} | |
:ready-for-construction {:construction-began :constructing} | |
:constructing {:inspection-violations :fixing-construction | |
:inspection-approval :constructing | |
:finish-constructing :finished-construction} | |
:fixing-construction {:inspection-violations :fixing-construction | |
:inspection-approval :constructing} | |
:finished-construction {:ribbon-cutting :complete} | |
:complete {} | |
:terminated {}}) | |
; Here's a function that will take an arbitrary state machine map like above, a current state within | |
; that machine, and an event that transitions to a new state. It returns the state after the event: | |
(defn next-state [process current-state event] | |
(get-in process [current-state event])) | |
; Here are some stubbed database functions. Flesh these out according to your database solution: | |
(defn get-building-by-id [id] | |
{:current-state :concept}) | |
(defn set-building-by-id [id building] | |
building) | |
; Finally, here's a function that retrieves a building's data and uses several events to transition | |
; the building from its current state to a new state. Any events that are illegal from a given | |
; state throw an error. | |
(defn process-events [building-id events] | |
(let [{:keys [current-state] :as building} (get-building-by-id building-id) | |
new-state (reduce | |
(fn [state event] | |
(or (next-state construction-process state event) | |
(throw (Exception. "Invalid event for the current state.")))) | |
current-state | |
events)] | |
(set-building-by-id building-id (assoc building :current-state new-state)))) | |
; And here's some code that exercises the above function. | |
(comment | |
(process-events 0 [:township-approved | |
:blueprints-delivered | |
:code-violations | |
; :blueprints-delivered | |
; :code-approval | |
; :construction-began | |
; :inspection-violations | |
; :inspection-approval | |
; :finish-constructing | |
; :ribbon-cutting | |
]) | |
) | |
; A few nice things about defining a multi-step process as a hash-map of states and event | |
; transitions: | |
; - Self-documenting for non-technical users | |
; - Can be used to generate flow charts (e.g. via graphviz) | |
; - Can be stored in a database or in document storage (e.g. S3) | |
; - Can be altered using a simple form UI |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment