Skip to content

Instantly share code, notes, and snippets.

@Janiczek
Created July 4, 2014 16:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Janiczek/576c553e700191fceb87 to your computer and use it in GitHub Desktop.
Save Janiczek/576c553e700191fceb87 to your computer and use it in GitHub Desktop.
;;; global state
;; pros/cons:
;; + the code looks nice
;; - can't control the state
(defn milk-the-cow []
(if (have? :bucket)
(do
(item-remove! :bucket)
(item-add! :bucket-of-milk))
(get-kicked!)))
;;; explicit state
;; pros/cons:
;; + can debug better by putting in our own state (no hidden parameters)
;; - have to pass the state as an argument everywhere
;; - we still change the state under our feet (thus, impure function)
(defn milk-the-cow [s]
(if (have? s :bucket)
(do
(item-remove! s :bucket)
(item-add! s :bucket-of-milk))
(get-kicked! s)))
;; (maybe returning state at the end of function could help a bit?)
;;; return-changed-state fn
;; pros/cons
;; + referential transparency
;; + pure function
;; - ?
(defn milk-the-cow [s]
(if (have? s :bucket)
(do
(-> s
(item-remove! :bucket)
(item-add! :bucket-of-milk)))
(get-kicked! s))) ;; or (-> s get-kicked!), if you prefer
@Janiczek
Copy link
Author

Janiczek commented Jul 4, 2014

When I look at it from a distance, it looks like the last two examples are almost the same.

The difference is that the 2nd example passes the old state (s) to both of the functions, instead of passing the return value from item-remove! to item-add!, like this:

(let [s1 (item-remove! s :bucket)
      s2 (item-add! s1 :bucket-of-milk)]
  s2)

Thus the 2nd example has the hidden assumption in it that the functions mutate the state (otherwise why would we even call the item-remove!?).

The last example, on the other hand, CAN have the assumption that the functions don't mutate anything - they return the modified state and expect their caller to do something with it.

@wagjo
Copy link

wagjo commented Jul 5, 2014

I don't see a pure function in the third example. item-remove! still looks like a mutable version (bang in the end). Anyway, you can add one more drawback to the mutable version, it contains race conditions, as the state is not changed atomically. My preferred solution is

(defn milk-the-cow [cowboy]
  (if (contains? cowboy :bucket)
    (do
      (-> cowboy
          (dissoc :bucket)
          (assoc :bucket-of-milk true)))
    (update-in cowboy [:kicked] #(inc (or % 0))))

(def cowboy (atom {:bucket true}))

(swap! cowboy milk-the-cow)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment