Monads in Clojure
Or why burritos shouldn’t be scary [I]
Leonardo Borges
@leonardo_borges
www.leonardoborges.com
www.thoug...
Monads in Clojure
The plan:
Type Signatures in Haskell
The Monad Type Class
A few useful monads

This presentation is a su...
Prelude

Type signatures in Haskell

Tuesday, 4 March 14
Type signatures in Haskell
Vectors as maps
import qualified Data.Map as Map -- just giving Data.Map an alias
mkVec :: a ->...
Type signatures in Haskell
Vectors as maps
import qualified Data.Map as Map -- just giving Data.Map an alias
mkVec :: a ->...
Type signatures in Haskell
Vectors as maps
import qualified Data.Map as Map -- just giving Data.Map an alias
mkVec :: a ->...
Type signatures in Haskell
Multiplication

(*) :: Num a => a -> a -> a

This is also a function of
two arguments of type a...
Type signatures in Haskell
Multiplication
Num a is a type
constraint
(*) :: Num a => a -> a -> a

In this case, the type c...
Monads
what on earth are they?

They’re simply an abstraction
...with a funny name to confuse people.

Tuesday, 4 March 14
Monads
what on earth are they?

They are not however....
... a burrito
... an elephant
... a space suit
... or a writing d...
Monads
what on earth are they?

Every monad has an associated “context”
It can be useful to think of them as “wrapping” so...
The Monad Type Class
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x ...
The Monad Type Class
return :: a -> m a

Single argument of type a

Tuesday, 4 March 14
The Monad Type Class
return :: a -> m a

returns a in a “context” m

Tuesday, 4 March 14
The Monad Type Class
return :: a -> m a
This is how we turn plain values into monadic values

Tuesday, 4 March 14
The Monad Type Class
return :: a -> m a
return

is an unfortunate name as it is confusing. It has
nothing to do with the r...
The Monad Type Class
(>>=) :: m a -> (a -> m b) -> m b
>>=

Tuesday, 4 March 14

is pronounced bind and takes two argument...
The Monad Type Class
(>>=) :: m a -> (a -> m b) -> m b

A monad

Tuesday, 4 March 14

m a
The Monad Type Class
(>>=) :: m a -> (a -> m b) -> m b

and a function from values of type a
to monads of type m

Tuesday,...
The Monad Type Class
(>>=) :: m a -> (a -> m b) -> m b

Somehow each monad knows how to extract values from
its context an...
The Monad Type Class
(>>=) :: m a -> (a -> m b) -> m b

and it returns b in a
“context” m

Tuesday, 4 March 14
The Monad Type Class
(>>) :: m a -> m b -> m b
x >> y = x >>= _ -> y
You can work out the type signature all by yourself n...
The Monad Type Class
(>>) :: m a -> m b -> m b
x >> y = x >>= _ -> y
>> is pronounced then

and includes a default

implem...
The Monad Type Class
(>>) :: m a -> m b -> m b
x >> y = x >>= _ -> y

Its second argument to bind is a
function which igno...
The Monad Type Class
(>>) :: m a -> m b -> m b
x >> y = x >>= _ -> y

It simply returns the value yielded by
“unwrapping” ...
Example I
Calculating all combinations between elements of two lists using the
list monad
combinations :: [(Int,Int)]
comb...
Example I
Calculating all combinations between elements of two lists using the
list monad

meh. It’s damn hard to read.
An...
Example I
Calculating all combinations between elements of two lists using the
list monad (using do notation)
combinations...
You’ve just seen your first monad, the list monad!

Tuesday, 4 March 14
The Monad Type Class *
As if it worked for lists only

class Monad [] where
return :: a -> [a]
(>>=) :: [a] -> (a -> [b]) ...
Now let’s learn how it does its thing...
...in Clojure!

Tuesday, 4 March 14
The List Monad
Let’s do it together

(def list-m {
:return (fn [v] ...
:bind
(fn [mv f]
...
})

Tuesday, 4 March 14

)
)
The List Monad
Let’s do it together

(def list-m {
:return (fn [v] (list v))
:bind
(fn [mv f]
...
)
})

Tuesday, 4 March 1...
The List Monad
Let’s do it together

(def list-m {
:return (fn [v] (list v))
:bind
(fn [mv f]
(mapcat f mv))
})

Tuesday, ...
The List Monad
Now we’re ready to use it
(defn combinations []
(let [bind
(:bind list-m)
return (:return list-m)]
(bind [1...
The List Monad
Now we’re ready to use it

ugh. Just as ugly.
But we know there’s a better way.
Can we use the do notation ...
Macros to the rescue
Haskell’s do notation in Clojure
(defn m-steps [m [name val & bindings] body]
(if (seq bindings)
`(->...
Macros to the rescue
Haskell’s do notation in Clojure

Ignore the implementation. You can study it later :)

Tuesday, 4 Ma...
The List Monad
Now we’re ready to use it
(defn combinations []
(do-m list-m
[a [1 2 3]
b [4 5]]
[a b]))
;; ([1 4] [1 5] [2...
Familiar?
List comprehension

(defn combinations []
(for [a [1 2 3]
b [4 5]]
[a b]))
;; ([1 4] [1 5] [2 4] [2 5] [3 4] [3 ...
Example II
Adding two numbers
(defn add [a b]
(+ a b))
(add 1 2) ;; 3

(add 1 nil)
;; NullPointerException

Tuesday, 4 Mar...
Example II
[Maybe]Adding two numbers
(defn m-add [ma mb]
(do-m maybe-m [a ma
b mb]
(+ a b)))
(m-add 1 2) ;; 3
(m-add nil 1...
The Maybe Monad
Again, everyone together

(def maybe-m {
:return (fn [v] ...)
:bind (fn [mv f]
...
)
})

Tuesday, 4 March ...
The Maybe Monad
Again, everyone together

(def maybe-m {
:return (fn [v] v)
:bind (fn [mv f]
...
)
})

Tuesday, 4 March 14
The Maybe Monad
Again, everyone together

(def maybe-m {
:return (fn [v] v)
:bind (fn [mv f]
(when mv
(f mv)))
})

Tuesday...
Not too bad, huh? Ready for more?

Tuesday, 4 March 14
Example III
Application configuration
(defn connect-to-db [env]
(let [db-uri (:db-uri env)]
(prn (format "Connected to db a...
Example III
Application configuration
(defn run-app [env]
(do
(connect-to-db env)
(connect-to-api env)
"Done."))

(run-app ...
Example III
Application configuration

Passing env on to every single function that
depends on it can be cumbersome.
Obviou...
Example III
Application configuration with the Reader Monad
(defn connect-to-db []
(do-m reader-m
[db-uri (asks :db-uri)]
(...
Example III
Application configuration with the Reader Monad
(defn run-app []
(do-m reader-m
[_ (m-connect-to-db)
_ (m-conne...
Example III
Application configuration with the Reader Monad

But, how how does it work?!

Tuesday, 4 March 14
The Reader Monad
Tricky one, but not that tricky

(defn ask [] identity)
(defn asks [f]
(fn [env]
(f env)))

The function ...
The Reader Monad
Tricky one, but not that tricky

(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (...
The Reader Monad
Expanding connect-to-db
This:
(defn connect-to-db []
(do-m reader-m
[db-uri (asks :db-uri)]
(prn (format ...
The Reader Monad
Expanding connect-to-db
(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (m r)) r))...
The Reader Monad
Expanding connect-to-db
(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (m r)) r))...
The Reader Monad
Expanding connect-to-db
(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (m r)) r))...
The Reader Monad
Expanding connect-to-db
(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (m r)) r))...
The Reader Monad
Expanding connect-to-db
(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (m r)) r))...
The Reader Monad
Expanding connect-to-db
(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (m r)) r))...
The Reader Monad
Expanding connect-to-db
(def reader-m
{:return (fn [a]
(fn [_] a))
:bind (fn [m k]
(fn [r]
((k (m r)) r))...
The Reader Monad

Simple, right? :)

Tuesday, 4 March 14
Example IV
Dependency Injection

(require '[app.repository :as repository])
(defn clone! [id user]
(let [old-user (reposit...
Example IV
Dependency Injection: the problem

• clone! is tightly coupled to the repository namespace
• It can’t be tested...
Example IV
Dependency Injection using the Reader Monad

Let’s start by turning our
repository into a module that can
be in...
Example IV
Dependency Injection using the Reader Monad

There’s a couple of ways in
which we can do that
I’ll use protocol...
Example IV
Dependency Injection using the Reader Monad
(defprotocol UserRepository
(fetch-by-id! [this id])
(create! [this...
Example IV
Dependency Injection using the Reader Monad

Remember: One of the advantages of the reader
monad is not having ...
Example IV
Dependency Injection using the Reader Monad

With that in mind, let’s rewrite clone!

Tuesday, 4 March 14
Example IV
Dependency Injection using the Reader Monad
It can be helpful to think of clone as having this type signature:
...
Example IV
Dependency Injection using the Reader Monad
And this is how we might use it:
(defn run-clone []
(do-m reader-m
...
The Reader Monad
A few observations from a clojure perspective

• Useful abstraction for computations that read values fro...
Monads: Summary
• Lots of useful abstractions through a common interface
• Some monads, such as Maybe and Either, are a lo...
Questions?
Leonardo Borges
@leonardo_borges
www.leonardoborges.com
www.thoughtworks.com

Tuesday, 4 March 14
Upcoming SlideShare
Loading in...5
×

Monads in Clojure

2,436

Published on

Talk given at the Sydney Clojure User group in February 2014

Published in: Technology

Monads in Clojure

  1. 1. Monads in Clojure Or why burritos shouldn’t be scary [I] Leonardo Borges @leonardo_borges www.leonardoborges.com www.thoughtworks.com [I] http://blog.plover.com/prog/burritos.html Tuesday, 4 March 14
  2. 2. Monads in Clojure The plan: Type Signatures in Haskell The Monad Type Class A few useful monads This presentation is a super condensed version of my blog post series on monads [II] [II] http://bit.ly/monads-part-i Tuesday, 4 March 14
  3. 3. Prelude Type signatures in Haskell Tuesday, 4 March 14
  4. 4. Type signatures in Haskell Vectors as maps import qualified Data.Map as Map -- just giving Data.Map an alias mkVec :: a -> a -> Map.Map [Char] a -- this is the type signature This is a function of two arguments of type a Tuesday, 4 March 14
  5. 5. Type signatures in Haskell Vectors as maps import qualified Data.Map as Map -- just giving Data.Map an alias mkVec :: a -> a -> Map.Map [Char] a -- this is the type signature It returns a Map where keys are of type [Char] and values are of type a Tuesday, 4 March 14
  6. 6. Type signatures in Haskell Vectors as maps import qualified Data.Map as Map -- just giving Data.Map an alias mkVec :: a -> a -> Map.Map [Char] a -- this is the type signature mkVec x y = Map.fromList [("x", x), ("y", y)] -- this is the implementation. You can ignore this part. -- using it myVec = mkVec 10 15 Map.lookup "x" myVec -- Just 10 Tuesday, 4 March 14
  7. 7. Type signatures in Haskell Multiplication (*) :: Num a => a -> a -> a This is also a function of two arguments of type a Tuesday, 4 March 14
  8. 8. Type signatures in Haskell Multiplication Num a is a type constraint (*) :: Num a => a -> a -> a In this case, the type constraint tells the compiler that (*)works on any value a as long as a is an instance of Num Tuesday, 4 March 14
  9. 9. Monads what on earth are they? They’re simply an abstraction ...with a funny name to confuse people. Tuesday, 4 March 14
  10. 10. Monads what on earth are they? They are not however.... ... a burrito ... an elephant ... a space suit ... or a writing desk. Tuesday, 4 March 14
  11. 11. Monads what on earth are they? Every monad has an associated “context” It can be useful to think of them as “wrapping” some value Tuesday, 4 March 14
  12. 12. The Monad Type Class class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b x >> y = x >>= _ -> y Let’s understand those type signatures! Tuesday, 4 March 14
  13. 13. The Monad Type Class return :: a -> m a Single argument of type a Tuesday, 4 March 14
  14. 14. The Monad Type Class return :: a -> m a returns a in a “context” m Tuesday, 4 March 14
  15. 15. The Monad Type Class return :: a -> m a This is how we turn plain values into monadic values Tuesday, 4 March 14
  16. 16. The Monad Type Class return :: a -> m a return is an unfortunate name as it is confusing. It has nothing to do with the return keywords in languages like Java. return is sometimes called unit Tuesday, 4 March 14
  17. 17. The Monad Type Class (>>=) :: m a -> (a -> m b) -> m b >>= Tuesday, 4 March 14 is pronounced bind and takes two arguments
  18. 18. The Monad Type Class (>>=) :: m a -> (a -> m b) -> m b A monad Tuesday, 4 March 14 m a
  19. 19. The Monad Type Class (>>=) :: m a -> (a -> m b) -> m b and a function from values of type a to monads of type m Tuesday, 4 March 14 b
  20. 20. The Monad Type Class (>>=) :: m a -> (a -> m b) -> m b Somehow each monad knows how to extract values from its context and “bind” them to the given function Tuesday, 4 March 14
  21. 21. The Monad Type Class (>>=) :: m a -> (a -> m b) -> m b and it returns b in a “context” m Tuesday, 4 March 14
  22. 22. The Monad Type Class (>>) :: m a -> m b -> m b x >> y = x >>= _ -> y You can work out the type signature all by yourself now :) Tuesday, 4 March 14
  23. 23. The Monad Type Class (>>) :: m a -> m b -> m b x >> y = x >>= _ -> y >> is pronounced then and includes a default implementation in terms of bind Tuesday, 4 March 14 (>>=)
  24. 24. The Monad Type Class (>>) :: m a -> m b -> m b x >> y = x >>= _ -> y Its second argument to bind is a function which ignores its argument Tuesday, 4 March 14
  25. 25. The Monad Type Class (>>) :: m a -> m b -> m b x >> y = x >>= _ -> y It simply returns the value yielded by “unwrapping” the context of y Tuesday, 4 March 14
  26. 26. Example I Calculating all combinations between elements of two lists using the list monad combinations :: [(Int,Int)] combinations = [1, 2, 3] >>= a -> [4, 5] >>= b -> return (a,b) -- [(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)] Tuesday, 4 March 14
  27. 27. Example I Calculating all combinations between elements of two lists using the list monad meh. It’s damn hard to read. And we don’t want to type that much. There’s a better way Tuesday, 4 March 14
  28. 28. Example I Calculating all combinations between elements of two lists using the list monad (using do notation) combinations' :: [(Int,Int)] combinations' = do a <- [1, 2, 3] b <- [4, 5] return (a,b) -- [(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)] Tuesday, 4 March 14
  29. 29. You’ve just seen your first monad, the list monad! Tuesday, 4 March 14
  30. 30. The Monad Type Class * As if it worked for lists only class Monad [] where return :: a -> [a] (>>=) :: [a] -> (a -> [b]) -> [b] (>>) :: [a] -> [b] -> [b] * this isn’t valid Haskell. It’s just to illustrate the concept Tuesday, 4 March 14
  31. 31. Now let’s learn how it does its thing... ...in Clojure! Tuesday, 4 March 14
  32. 32. The List Monad Let’s do it together (def list-m { :return (fn [v] ... :bind (fn [mv f] ... }) Tuesday, 4 March 14 ) )
  33. 33. The List Monad Let’s do it together (def list-m { :return (fn [v] (list v)) :bind (fn [mv f] ... ) }) Tuesday, 4 March 14
  34. 34. The List Monad Let’s do it together (def list-m { :return (fn [v] (list v)) :bind (fn [mv f] (mapcat f mv)) }) Tuesday, 4 March 14
  35. 35. The List Monad Now we’re ready to use it (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]))))))) ;; ([1 4] [1 5] [2 4] [2 5] [3 4] [3 5]) Tuesday, 4 March 14
  36. 36. The List Monad Now we’re ready to use it ugh. Just as ugly. But we know there’s a better way. Can we use the do notation in Clojure? Tuesday, 4 March 14
  37. 37. Macros to the rescue Haskell’s do notation in Clojure (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)) Tuesday, 4 March 14
  38. 38. Macros to the rescue Haskell’s do notation in Clojure Ignore the implementation. You can study it later :) Tuesday, 4 March 14
  39. 39. The List Monad Now we’re ready to use it (defn combinations [] (do-m list-m [a [1 2 3] b [4 5]] [a b])) ;; ([1 4] [1 5] [2 4] [2 5] [3 4] [3 5]) Tuesday, 4 March 14
  40. 40. Familiar? List comprehension (defn combinations [] (for [a [1 2 3] b [4 5]] [a b])) ;; ([1 4] [1 5] [2 4] [2 5] [3 4] [3 5]) Tuesday, 4 March 14
  41. 41. Example II Adding two numbers (defn add [a b] (+ a b)) (add 1 2) ;; 3 (add 1 nil) ;; NullPointerException Tuesday, 4 March 14
  42. 42. Example II [Maybe]Adding two numbers (defn m-add [ma mb] (do-m maybe-m [a ma b mb] (+ a b))) (m-add 1 2) ;; 3 (m-add nil 1) ;; nil Tuesday, 4 March 14
  43. 43. The Maybe Monad Again, everyone together (def maybe-m { :return (fn [v] ...) :bind (fn [mv f] ... ) }) Tuesday, 4 March 14
  44. 44. The Maybe Monad Again, everyone together (def maybe-m { :return (fn [v] v) :bind (fn [mv f] ... ) }) Tuesday, 4 March 14
  45. 45. The Maybe Monad Again, everyone together (def maybe-m { :return (fn [v] v) :bind (fn [mv f] (when mv (f mv))) }) Tuesday, 4 March 14
  46. 46. Not too bad, huh? Ready for more? Tuesday, 4 March 14
  47. 47. Example III Application configuration (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)))) Tuesday, 4 March 14
  48. 48. Example III Application configuration (defn run-app [env] (do (connect-to-db env) (connect-to-api env) "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." Tuesday, 4 March 14
  49. 49. Example III Application configuration Passing env on to every single function that depends on it can be cumbersome. Obviously we don’t want to resort to global vars. Can monads help? Tuesday, 4 March 14
  50. 50. Example III Application configuration with the Reader Monad (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)))) Bear with me for now. These will be explained soon. Tuesday, 4 March 14
  51. 51. Example III Application configuration with the Reader Monad (defn run-app [] (do-m reader-m [_ (m-connect-to-db) _ (m-connect-to-api)] (prn "Done."))) Note the env parameter disappeared. The Reader monad somehow feeds that into our functions. ((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." Also this is now a function call as run-app is now pure Tuesday, 4 March 14
  52. 52. Example III Application configuration with the Reader Monad But, how how does it work?! Tuesday, 4 March 14
  53. 53. The Reader Monad Tricky one, but not that tricky (defn ask [] identity) (defn asks [f] (fn [env] (f env))) The function names come from Haskell. They are defined in the MonadReader type class. Tuesday, 4 March 14
  54. 54. The Reader Monad Tricky one, but not that tricky (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) There’s a lot going on in bind. Let’s unravel it. Tuesday, 4 March 14
  55. 55. The Reader Monad Expanding connect-to-db This: (defn connect-to-db [] (do-m reader-m [db-uri (asks :db-uri)] (prn (format "Connected to db at %s" db-uri)))) Expands into: ((:bind reader-m) (asks :db-uri) (fn* ([db-uri] ((:return reader-m) (prn "Connected to db at " db-uri))))) Tuesday, 4 March 14
  56. 56. The Reader Monad Expanding connect-to-db (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) (let [m (fn [env] (:db-uri env)) k (fn* ([db-uri] ((:return reader-m) (prn "Connected to db at " db-uri))))] ((fn [r] ((k (m r)) r)) {:db-uri "user:passwd@host/dbname" :api-key "AF167"})) Tuesday, 4 March 14
  57. 57. The Reader Monad Expanding connect-to-db (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) (let [m (fn [env] (:db-uri env)) k (fn* ([db-uri] ((:return reader-m) (prn "Connected to db at " db-uri))))] (let [r {:db-uri "user:passwd@host/dbname" :api-key "AF167"}] ((k (m r)) r))) Tuesday, 4 March 14
  58. 58. The Reader Monad Expanding connect-to-db (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) (let [m (fn [env] (:db-uri env)) k (fn* ([db-uri] ((:return reader-m) (prn "Connected to db at " db-uri))))] (let [r {:db-uri "user:passwd@host/dbname" :api-key "AF167"}] ((k "user:passwd@host/dbname") r))) Tuesday, 4 March 14
  59. 59. The Reader Monad Expanding connect-to-db (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) (let [m (fn [env] (:db-uri env)) k (fn* ([db-uri] ((:return reader-m) nil)))] (let [r {:db-uri "user:passwd@host/dbname" :api-key "AF167"}] ((k "user:passwd@host/dbname") r))) Tuesday, 4 March 14
  60. 60. The Reader Monad Expanding connect-to-db (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) (let [m (fn [env] (:db-uri env)) k (fn* ([db-uri] ((fn [a] (fn [_] a) nil)))] (let [r {:db-uri "user:passwd@host/dbname" :api-key "AF167"}] ((k "user:passwd@host/dbname") r))) Tuesday, 4 March 14
  61. 61. The Reader Monad Expanding connect-to-db (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) (let [m (fn [env] (:db-uri env)) k (fn* ([db-uri] ((fn [a] (fn [_] a) nil)))] (let [r {:db-uri "user:passwd@host/dbname" :api-key "AF167"}] ((fn [_] nil) r))) Tuesday, 4 March 14
  62. 62. The Reader Monad Expanding connect-to-db (def reader-m {:return (fn [a] (fn [_] a)) :bind (fn [m k] (fn [r] ((k (m r)) r)))}) (let [m (fn [env] (:db-uri env)) k (fn* ([db-uri] ((fn [a] (fn [_] a) nil)))] (let [r {:db-uri "user:passwd@host/dbname" :api-key "AF167"}] nil)) Tuesday, 4 March 14
  63. 63. The Reader Monad Simple, right? :) Tuesday, 4 March 14
  64. 64. Example IV Dependency Injection (require '[app.repository :as repository]) (defn clone! [id user] (let [old-user (repository/fetch-by-id id {}) cloned-user (repository/create! (compute-clone old-user) user) updated-user (assoc old-user :clone-id (:id cloned-user))] (repository/update! old-user updated-user user))) See the problem? Tuesday, 4 March 14
  65. 65. Example IV Dependency Injection: the problem • clone! is tightly coupled to the repository namespace • It can’t be tested in isolation without resorting to heavy mocking of the involved functions • It’s limited to a single repository implementation - makes experimenting at the REPL harder • ... Basically, good software engineering principles apply Tuesday, 4 March 14
  66. 66. Example IV Dependency Injection using the Reader Monad Let’s start by turning our repository into a module that can be injected into our function Tuesday, 4 March 14
  67. 67. Example IV Dependency Injection using the Reader Monad There’s a couple of ways in which we can do that I’ll use protocols as they have added performance benefits Tuesday, 4 March 14
  68. 68. Example IV Dependency Injection using the Reader Monad (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)))) Tuesday, 4 March 14
  69. 69. Example IV Dependency Injection using the Reader Monad Remember: One of the advantages of the reader monad is not having to pass parameters around to every single function that needs it Tuesday, 4 March 14
  70. 70. Example IV Dependency Injection using the Reader Monad With that in mind, let’s rewrite clone! Tuesday, 4 March 14
  71. 71. Example IV Dependency Injection using the Reader Monad It can be helpful to think of clone as having this type signature: ;; clone! :: Number -> String -> Reader UserRepository Number (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)))) Tuesday, 4 March 14
  72. 72. Example IV Dependency Injection using the Reader Monad And this is how we might use it: (defn run-clone [] (do-m reader-m [_ (clone! 10 "leo")] (prn "Cloned."))) ((run-clone) (mk-user-repo)) ;; ;; ;; ;; ;; Tuesday, 4 March 14 "Fetched user id " 10 "Compute clone for user" "Create user triggered by " "leo" "Updated user triggered by " "leo" "Cloned."
  73. 73. The Reader Monad A few observations from a clojure perspective • Useful abstraction for computations that read values from a shared environment • As we saw, it has multiple use cases such as configuration and dependency injection • I’ve cheated in the examples: • Each function using the reader monad also performed IO - via prn • In Haskell, IO is a monad so we would need to use monad transformers to compose them - beyond the scope of this presentation Tuesday, 4 March 14
  74. 74. Monads: Summary • Lots of useful abstractions through a common interface • Some monads, such as Maybe and Either, are a lot more powerful in a statically typed language like Haskell or Scala due to its usage being encoded in the type system, as well as native support to pattern matching • Others, such as the Reader monad, can be very useful in a dynamic setting - though you don’t have the type system to tell you when you stuff up at compile time • In such cases, type annotations in comments can be useful Tuesday, 4 March 14
  75. 75. Questions? Leonardo Borges @leonardo_borges www.leonardoborges.com www.thoughtworks.com Tuesday, 4 March 14
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×