Book Image

Clojure Reactive Programming

Book Image

Clojure Reactive Programming

Overview of this book

Table of Contents (19 chapters)
Clojure Reactive Programming
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Bibliography
Index

Monads


Our last abstraction will solve the very problem we raised in the previous section: how to safely perform intermediate calculations by preserving the semantics of the abstractions we're working with—in this case, options.

It should be no surprise now that fluokitten also provides a protocol for Monads, simplified and shown as follows:

(defprotocol Monad
  (bind [mv g]))

If you think in terms of a class hierarchy, Monads would be at the bottom of it, inheriting from Applicative Functors, which, in turn, inherit from Functors. That is, if you're working with a Monad, you can assume it is also an Applicative and a Functor.

The bind function of monads takes a function g as its second argument. This function receives as input the value contained in mv and returns another Monad containing its result. This is a crucial part of the contract: g has to return a Monad.

The reason why will become clearer after some examples. But first, let's promote our Option abstraction to a Monad—at this point, Option is already an Applicative Functor and a Functor:

(extend-protocol fkp/Monad
  Some
  (bind [mv g]
    (g (:v mv)))

  None
  (bind [_ _]
    (None.)))

The implementation is fairly simple. In the None version, we can't really do anything, so just like we have been doing so far, we return an instance of None.

The Some implementation extracts the value from the Monad mv and applies the function g to it. Note how this time we don't need to wrap the result as the function g already returns a Monad instance.

Using the Monad API, we could sum the ages of our pirates as follows:

(def opt-ctx (None.))

(fkc/bind (age-option "Jack Sparrow")
          (fn [a]
            (fkc/bind (age-option "Blackbeard")
                      (fn [b]
                        (fkc/bind (age-option "Hector Barbossa")
                                  (fn [c]
                                    (fkc/pure opt-ctx 
                                              (+ a b c))))))))
;; #library_design.option.Some{:v 170.0}

Firstly, we are making use of Applicative's pure function in the inner-most function. Remember that role of pure is to provide a generic way to put a value into an Applicative Functor. Since Monads are also Applicative, we make use of them here.

However, since Clojure is a dynamically typed language, we need to hint pure with the context—container—type we wish to use. This context is simply an instance of either Some or None. They both have the same pure implementation.

While we do get the right answer, the preceding example is far from what we would like to write due to its excessive nesting. It is also hard to read.

Thankfully, fluokitten provides a much better way to write monadic code, called the do-notation:

(fkc/mdo [a (age-option "Jack Sparrow")
          b (age-option "Blackbeard")
          c (age-option "Hector Barbossa")]
         (fkc/pure opt-ctx  (+ a b c)))
;; #library_design.option.Some{:v 170.0}

Suddenly, the same code becomes a lot cleaner and easier to read, without losing any of the semantics of the Option Monad. This is because mdo is a macro that expands to the code equivalent of the nested version, as we can verify by expanding the macro as follows:

(require '[clojure.walk :as w])

(w/macroexpand-all '(fkc/mdo [a (age-option "Jack Sparrow")
                              b (age-option "Blackbeard")
                              c (age-option "Hector Barbossa")]
                             (option  (+ a b c))))
;; (uncomplicate.fluokitten.core/bind
;;  (age-option "Jack Sparrow")
;;  (fn*
;;   ([a]
;;    (uncomplicate.fluokitten.core/bind
;;     (age-option "Blackbeard")
;;     (fn*
;;      ([b]
;;       (uncomplicate.fluokitten.core/bind
;;        (age-option "Hector Barbossa")
;;        (fn* ([c] (fkc/pure opt-ctx (+ a b c)))))))))))

Tip

It is important to stop for a moment here and appreciate the power of Clojure—and Lisp in general.

Languages such as Haskell and Scala, which make heavy use of abstractions such as Functors, Applicative, and Monads, also have their own versions of the do-notation. However, this support is baked into the compiler itself.

As an example, when Haskell added do-notation to the language, a new version of the compiler was released, and developers wishing to use the new feature had to upgrade.

In Clojure, on the other hand, this new feature can be shipped as a library due to the power and flexibility of macros. This is exactly what fluokitten has done.

Now, we are ready to go back to our original problem, gathering stats about the pirates' ages.

First, we will define a couple of helper functions that convert the result of our stats functions into the Option Monad:

(def avg-opt     (comp option avg))
(def median-opt  (comp option median))
(def std-dev-opt (comp option std-dev))

Here, we take advantage of function composition to create monadic versions of existing functions.

Next, we will rewrite our solution using the monadic do-notation we learned earlier:

(fkc/mdo [a       (age-option "Jack Sparrow")
          b       (age-option "Blackbeard")
          c       (age-option "Hector Barbossa")
          avg     (avg-opt a b c)
          median  (median-opt a b c)
          std-dev (std-dev-opt a b c)]
         (option {:avg avg
                  :median median
                  :std-dev std-dev}))
;; #library_design.option.Some{:v {:avg 56.666668,
;;                                 :median 60,
;;                                 :std-dev 12.472191289246473}}

This time we were able to write the function as we normally would, without having to worry about whether any values in the intermediate computations are empty or not. This semantic that is the very essence of the Option Monad is still preserved, as can be seen in the following:

(fkc/mdo [a       (age-option "Jack Sparrow")
          b       (age-option "Blackbeard")
          c       (age-option "Hector Barbossa")
          avg     (avg-opt a b c)
          median  (median-opt a b c)
          std-dev (std-dev-opt a b c)]
         (fkc/pure opt-ctx {:avg avg
                  :median median
                  :std-dev std-dev}))
;; #library_design.option.None{}

For the sake of completeness, we will use futures to demonstrate how the do-notation works for any Monad:

(def avg-fut     (comp i/future-call avg))
(def median-fut  (comp i/future-call median))
(def std-dev-fut (comp i/future-call std-dev))

(fkc/mdo [a       (i/future (some-> (pirate-by-name "Jack Sparrow") age))
          b       (i/future (some-> (pirate-by-name "Blackbeard") age))
          c       (i/future (some-> (pirate-by-name "Hector Barbossa") age))
          avg     (avg-fut a b c)
          median  (median-fut a b c)
          std-dev (std-dev-fut a b c)]
         (i/const-future {:avg avg
                          :median median
                          :std-dev std-dev}))
;; #<Future@3fd0b0d0: #<Success@1e08486b: {:avg 56.666668,
;;                                         :median 60,
;;                                         :std-dev 12.472191289246473}>>