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
Hands-On Reactive Programming with Clojure

You're reading from   Hands-On Reactive Programming with Clojure Create asynchronous, event-based, and concurrent applications

Arrow left icon
Product type Paperback
Published in Jan 2019
Publisher Packt
ISBN-13 9781789346138
Length 298 pages
Edition 2nd Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Konrad Szydlo Konrad Szydlo
Author Profile Icon Konrad Szydlo
Konrad Szydlo
Leonardo Borges Leonardo Borges
Author Profile Icon Leonardo Borges
Leonardo Borges
Arrow right icon
View More author details
Toc

Table of Contents (20) Chapters Close

Title Page
Copyright and Credits
About Packt
Contributors
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. A New Approach to Futures 9. A Reactive API to Amazon Web Services 10. Reactive Microservices 11. Testing Reactive Apps 12. Concurrency Utilities in Clojure 1. Appendix - The Algebra of Library Design 2. Other Books You May Enjoy Index

Monads


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

It should be no surprise by 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 that 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 a number of 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 that 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} 

First, we make use of the applicative's pure function in the innermost function. Remember that the 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 purewith a type of context 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, as follows:

(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))))))))))) 

Note

It is important to stop for a moment 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, applicatives, 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 will 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 in order to create monadic versions of existing functions.

Next, we will rewrite our solution by using the monadic do-notation that 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. This semantic (that is the very essence of the option monad) is still preserved, and can be seen as follows:

(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}>>

Note

Please note that the preceding code snippet generates a varying result.

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