Handling errors with purity in Clojure?

564 views Asked by At

I'm working on a game using the big-bang style of programming where one defines the entire state as a single data structure and manages state change by swapping against a single atom.

In a game we cannot trust data sent by the client thus the server has to anticipate the possibility that some moves will be invalid and guard against it. When one writes a function for swapping world state it can start out pure; however, one must consider the possibility of invalid requests. I believe to effect the unhappy path in idiomatic Clojure one simply throws exceptions. So now functions that might have been pure are infected with side-effecting exceptions. Perhaps this is as it has to be.

(try
  (swap! world update-game) ;possible exception here!
  (catch Exception e
    (>! err-chan e))

My aim is to defer side-effects until the last possible moment. I've hardly forayed into Haskell land but I know the concept of the Either monad. When one considers the happy and unhappy path one understands there are always these 2 possibilities. This has me thinking that swap! by itself is insufficient since it ignores the unhappy path. Here's the spirt of the idea:

(swap-either! err-chan world update-game) ;update-game returns either monad

Has the Clojure community adopted any more functional approaches for handling exceptions?

1

There are 1 answers

0
Arthur Ulfeldt On

I tend to take a couple different approaches in cases like this. If the state is being updated in a single location I tend to go with:

(try
  (swap! world update-game) ;possible exception here!
  (catch Exception e
    (>! err-chan e) 
    world)  ;;  <--- return the world unchanged

or if it's set in lots of places ad a watcher that throws the exception back to the place where swap! was called and doesn't change the state:

user> (def a (atom 1))
#'user/a
user> (add-watch a :im-a-test (fn [_ _ _ new-state]
                                (if (even? new-state)
                                  (throw (IllegalStateException. "I don't like even numbers")))))
#object[clojure.lang.Atom 0x5c1dc37e {:status :ready, :val 1}]
user> (swap! a + 2)
3
user> (swap! a + 3)
IllegalStateException I don't like even numbers  user/eval108260/fn--108261 (form-init8563497779572341831.clj:2)