Skip to content

Instantly share code, notes, and snippets.

@theleoborges
Last active August 29, 2015 13:56
Show Gist options
  • Save theleoborges/9344290 to your computer and use it in GitHub Desktop.
Save theleoborges/9344290 to your computer and use it in GitHub Desktop.
Monads in Clojure talk
;; 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