Skip to content

Instantly share code, notes, and snippets.

@logaan

logaan/core.clj Secret

Created October 3, 2012 12:42
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save logaan/49d9618add8ba81f6e41 to your computer and use it in GitHub Desktop.
Save logaan/49d9618add8ba81f6e41 to your computer and use it in GitHub Desktop.
(clojure.core/remove-ns 'monads-motherfucker.core)
(ns monads-motherfucker.core
(use midje.sweet
clojure.algo.monads))
(def colleen
{:name "Colleen"
:pet
{:name "Ebony"
:mother
{:name "Fluffy"
:owner
{:name "Sarah"}}}})
(def richard
{:name "Richard"})
(defn get-breeder-name [pet-owner]
((((pet-owner :pet) :mother) :owner) :name))
(fact (get-breeder-name colleen) => "Sarah")
(fact (get-breeder-name richard) => (throws java.lang.NullPointerException))
;; :(
(defn nil-friendly-get-breeder-name [pet-owner]
(if (nil? pet-owner) nil
(let [pet (pet-owner :pet)]
(if (nil? pet) nil
(let [mother (pet :mother)]
(if (nil? mother) nil
(let [owner (mother :owner)]
(if (nil? owner) nil
(owner :name)))))))))
(fact (nil-friendly-get-breeder-name colleen) => "Sarah")
(fact (nil-friendly-get-breeder-name richard) => nil)
;;
(defn call-unless-nil [function value]
(if (nil? value) nil
(function value)))
(fact (call-unless-nil (fn [v] "Called") nil) => nil)
(fact (call-unless-nil (fn [v] "Called") 1 ) => "Called")
(defn pretty-nil-friendly-get-breeder-name [pet-owner]
(call-unless-nil (fn [owner]
(call-unless-nil (fn [pet]
(call-unless-nil (fn [mother]
(call-unless-nil (fn [owner]
(owner :name))
(mother :owner)))
(pet :mother)))
(owner :pet)))
pet-owner))
(fact (pretty-nil-friendly-get-breeder-name colleen) => "Sarah")
(fact (pretty-nil-friendly-get-breeder-name richard) => nil)
;;
(def colin
{:name "Colin"
:clients [
{:name "Fred"
:investment-types [
{:type :gold
:markets [
{:name "Japan"
:value 7000}
{:name "America"
:value 8888}]}
{:type :shares
:markets [
{:name "China"
:value 3333}]}]}]})
(fact
(map :value
(mapcat :markets
(mapcat :investment-types
(colin :clients))))
=> [7000 8888 3333])
(fact
(mapcat (fn [market] (list (market :value)))
(mapcat :markets
(mapcat :investment-types
(colin :clients))))
=> [7000 8888 3333])
;; Defining the monads
(def list-chainer
{:step-runner mapcat :inner-wrapper list})
(def nil-chainer
{:step-runner call-unless-nil :inner-wrapper identity})
;; Defining chain
(defn flip [f]
(fn [& args] (apply f (reverse args))))
(defn wrap-last [wrapper steps]
(let [last (last steps)
wrapped (comp wrapper last)]
(replace {last wrapped} steps)))
(defn chain [{:keys [step-runner inner-wrapper]} value steps]
(reduce (flip step-runner) value (wrap-last inner-wrapper steps)))
; Note that `value` must be of the same type as all other values. For the nil
; chainer this is fine because colleen or richard could be nil.
(fact (chain nil-chainer colleen [:pet :mother :owner :name]) => "Sarah")
(fact (chain nil-chainer richard [:pet :mother :owner :name]) => nil)
; For list-chainer we need to call clients on colin first so that our value is
; a list.
(fact
(chain list-chainer (colin :clients) [:investment-types :markets :value])
=> [7000 8888 3333])
; I'd like to show you some places where you may already be using monads and
; outline some situations where you might consider it.
; `let` is basically just the identity monad's do function It performs no
; transformations and makes no decisions, but it gives you the ability to
; provide names for things and refrence those names through all your steps.
; Here I'm using `clojure.algo.monads` to demonstrate the similarities:
(fact
(let [foo 1
bar (inc foo)]
(+ foo bar))
=> 3)
(fact
(domonad identity-m
[foo 1
bar (inc foo)]
(+ foo bar))
=> 3)
; `for` is basically just the sequence monad's do function
(fact
(for [a [1 2 3]
b [4 5 6]]
(* a b))
=> [4 5 6 8 10 12 12 15 18])
(fact
(domonad sequence-m
[a [1 2 3]
b [4 5 6]]
(* a b))
=> [4 5 6 8 10 12 12 15 18])
; I use the threading functions in clojure a fair bit, they pass a value
; through a sequence of functions. Each return value gets passed to the next
; function:
(fact
(-> 1
inc
inc
((fn [v] (* v v))))
=> 9)
; Sometimes though I wish I could just define the pipeline of transformations
; without needing to pass in a value. I could use the chain function for the
; identity monad to accomplish this:
(def inc-inc-square
(with-monad identity-m
(m-chain [inc
inc
(fn [v] (* v v))])))
(fact (inc-inc-square 1) => 9)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment