Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Clojure Reactive Programming

You're reading from   Clojure Reactive Programming Design and implement highly reusable reactive applications by integrating different frameworks with Clojure

Arrow left icon
Product type Paperback
Published in Mar 2015
Publisher Packt
ISBN-13 9781783986668
Length 232 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Leonardo Borges Leonardo Borges
Author Profile Icon Leonardo Borges
Leonardo Borges
Arrow right icon
View More author details
Toc

Table of Contents (19) Chapters Close

Clojure Reactive Programming
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
1. What is Reactive Programming? FREE CHAPTER 2. A Look at Reactive Extensions 3. Asynchronous Programming and Networking 4. Introduction to core.async 5. Creating Your Own CES Framework with core.async 6. Building a Simple ClojureScript Game with Reagi 7. The UI as a Function 8. Futures 9. A Reactive API to Amazon Web Services The Algebra of Library Design 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}>>
lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime
Visually different images