Last active
August 29, 2015 13:56
-
-
Save theleoborges/9344290 to your computer and use it in GitHub Desktop.
Monads in Clojure talk
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
;; Slides at: http://www.slideshare.net/borgesleonardo/monads-in-clojure | |
;; The list Monad | |
(def list-m { | |
:return (fn [v] (list v)) | |
:bind (fn [mv f] | |
(mapcat f mv)) | |
}) | |
(defn combinations [] | |
(let [bind (:bind list-m) | |
return (:return list-m)] | |
(bind [1 2 3] | |
(fn [a] | |
(bind [4 5] | |
(fn [b] | |
(return [a b]))))))) | |
(combinations) | |
;; ([1 4] [1 5] [2 4] [2 5] [3 4] [3 5]) | |
;; In order to use the do notation, a simple | |
;; macro: | |
(defn m-steps [m [name val & bindings] body] | |
(if (seq bindings) | |
`(-> ~val | |
((:bind ~m) (fn [~name] | |
~(m-steps m bindings body)))) | |
`(-> ~val | |
((:bind ~m) (fn [~name] | |
((:return ~m) ~body)))))) | |
(defmacro do-m [m bindings body] | |
(m-steps m bindings body)) | |
;; Same function as above, but using the 'do-m' | |
;; macro | |
(defn combinations [] | |
(do-m list-m | |
[a [1 2 3] | |
b [4 5]] | |
[a b])) | |
(println (combinations)) | |
;; Very similar to list comprehensions! | |
(defn combinations* [] | |
(for [a [1 2 3] | |
b [4 5]] | |
[a b])) | |
;; Adding numbers | |
(defn add [a b] | |
(+ a b)) | |
;; (add 1 2) ;; 3 | |
;; (add 1 nil) ;; NullPointerException | |
;; Using the Maybe Monad | |
(def maybe-m | |
{:return (fn [v] v) | |
:bind (fn [mv f] | |
(when mv | |
(f mv)))}) | |
(defn m-add [ma mb] | |
(do-m maybe-m | |
[a ma | |
b mb] | |
(+ a b))) | |
;; (m-add 1 3) | |
;; (m-add nil 1) | |
;; | |
;;Dependency Injection | |
;; | |
;; Without the Reader monad | |
(defn connect-to-db [env] | |
(let | |
[db-uri (:db-uri env)] | |
(prn (format "Connected to db at %s" db-uri)))) | |
(defn connect-to-api [env] | |
(let | |
[api-key (:api-key env) | |
env (ask)] | |
(prn (format "Connected to api with key %s" api-key)))) | |
(defn run-app [env] | |
(do | |
(connect-to-db env) | |
(connect-to-api env) | |
(prn "Done."))) | |
(run-app {:db-uri "user:passwd@host/dbname" :api-key "AF167"}) | |
;; "Connected to db at user:passwd@host/dbname" | |
;; "Connected to api with key AF167" | |
;; "Done." | |
;; Reader Monad | |
(def reader-m | |
{:return (fn [a] | |
(fn [_] a)) | |
:bind (fn [m k] | |
(fn [r] | |
((k (m r)) r)))}) | |
(defn ask [] identity) | |
(defn asks [f] | |
(fn [env] | |
(f env))) | |
(defn connect-to-db [] | |
(do-m reader-m | |
[db-uri (asks :db-uri)] | |
(prn (format "Connected to db at %s" db-uri)))) | |
(defn connect-to-api [] | |
(do-m reader-m | |
[api-key (asks :api-key) | |
env (ask)] | |
(prn (format "Connected to api with key %s" api-key)))) | |
(defn run-app [] | |
(do-m reader-m | |
[_ (connect-to-db) | |
_ (connect-to-api)] | |
(prn "Done."))) | |
((run-app) {:db-uri "user:passwd@host/dbname" :api-key "AF167"}) | |
;; "Connected to db at user:passwd@host/dbname" | |
;; "Connected to api with key AF167" | |
;; "Done." | |
;; Another Dependency Injection Example | |
(defprotocol UserRepository | |
(fetch-by-id! [this id]) | |
(create! [this user username]) | |
(update! [this old-user new-user username])) | |
(defn mk-user-repo [] | |
(reify UserRepository | |
(fetch-by-id! [this id] | |
(prn "Fetched user id " id)) | |
(create! [this user username] | |
(prn "Create user triggered by " username)) | |
(update! [this old-user new-user username] | |
(prn "Updated user triggered by " username)))) | |
(defn compute-clone [user] | |
(prn "Compute clone for user")) | |
;; clone :: Number -> String -> Reader IUserRepository nil | |
(defn clone! [id user] | |
(do-m reader-m [repo (ask)] | |
(let [old-user (fetch-by-id! repo id) | |
cloned-user (create! repo (compute-clone old-user) user) | |
updated-user (assoc old-user :clone-id (:id cloned-user))] | |
(update! repo old-user updated-user user)))) | |
(defn run-clone [] | |
(do-m reader-m | |
[_ (clone! 10 "leo")] | |
(prn "Cloned."))) | |
((run-clone) (mk-user-repo)) | |
;; "Fetched user id " 10 | |
;; "Compute clone for user" | |
;; "Create user triggered by " "leo" | |
;; "Updated user triggered by " "leo" | |
;; "Cloned." | |
;; Check out https://github.com/clojure/algo.monads - it implements lots of common | |
;; and useful monads as well as a more robust version of the do notation macro |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment