-
-
Save logaan/49d9618add8ba81f6e41 to your computer and use it in GitHub Desktop.
This file contains 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
(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