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

Applicative Functors


Like Functors, Applicative Functors are a sort of container and defines two operations:

(defprotocol Applicative
  (pure [av v])
  (fapply [ag av]))

The pure function is a generic way to put a value inside an Applicative Functor. So far, we have been using the option helper function for this purpose. We will be using it a little later.

The fapply function will unwrap the function contained in the Applicative ag and apply it to the value contained in the applicative av.

The purpose of both the functions will become clear with an example, but first, we need to promote our Option Functor into an Applicative Functor:

(extend-protocol fkp/Applicative
  Some
  (pure [_ v]
    (Some. v))

  (fapply [ag av]
    (if-let [v (:v av)]
      (Some. ((:v ag) v))
      (None.)))

  None
  (pure [_ v]
    (Some. v))

  (fapply [ag av]
    (None.)))

The implementation of pure is the simplest. All it does is wrap the value v into an instance of Some. Equally simple is the implementation of fapply for None. As there is no value, we simply return None again.

The fapply implementation of Some ensures both arguments have a value for the :v keyword—strictly speaking they both have to be instances of Some. If :v is non-nil, it applies the function contained in ag to v, finally wrapping the result. Otherwise, it returns None.

This should be enough to try our first example using the Applicative Functor API:

(fkc/fapply (option inc) (option 2))
;; #library_design.option.Some{:v 3}

(fkc/fapply (option nil) (option 2))
;; #library_design.option.None{}

We are now able to work with Functors that contain functions. Additionally, we have also preserved the semantics of what should happen when any of the Functors don't have a value.

We can now revisit the age average example from before:

(def age-option (comp (partial fkc/fmap age) option pirate-by-name))

(let [a (age-option "Jack Sparrow")
      b (age-option "Blackbeard")
      c (age-option "Hector Barbossa")]
  (fkc/<*> (option (fkj/curry avg 3))
           a b c))
;; #library_design.option.Some{:v 56.666668}

Tip

The vararg function <*> is defined by fluokitten and performs a left-associative fapply on its arguments. Essentially, it is a convenience function that makes (<*> f g h) equivalent to (fapply (fapply f g) h).

We start by defining a helper function to avoid repetition. The age-option function retrieves the age of a pirate as an option for us.

Next, we curry the avg function to 3 arguments and put it into an option. Then, we use the <*> function to apply it to the options a, b, and c. We get to the same result, but have the Applicative Functor take care of nil values for us.

Tip

Function currying

Currying is the technique of transforming a function of multiple arguments into a higher-order function of a single argument that returns more single-argument functions until all arguments have been supplied.

Roughly speaking, currying makes the following snippets equivalent:

(def curried-1 (fkj/curry + 2))
(def curried-2 (fn [a]
                 (fn [b]
                   (+ a b))))

((curried-1 10) 20) ;; 30
((curried-2 10) 20) ;; 30

Using Applicative Functors this way is so common that the pattern has been captured as the function alift, as shown in the following:

 (defn alift
  "Lifts a n-ary function `f` into a applicative context"
  [f]
  (fn [& as]
    {:pre  [(seq as)]}
    (let [curried (fkj/curry f (count as))]
      (apply fkc/<*>
             (fkc/fmap curried (first as))
             (rest as)))))

The alift function is responsible for lifting a function in such a way that it can be used with Applicative Functors without much ceremony. Because of the assumptions we are able to make about Applicative Functors—for instance, that it is also a Functor—we can write generic code that can be re-used across any Applicatives.

With alift in place, our age average example turns into the following:

(let [a (age-option "Jack Sparrow")
      b (age-option "Blackbeard")
      c (age-option "Hector Barbossa")]
  ((alift avg) a b c))
;; #library_design.option.Some{:v 56.666668}

We lift avg into an Applicative compatible version, making the code look remarkably like simple function application. And since we are not doing anything interesting with the let bindings, we can simplify it further as follows:

((alift avg) (age-option "Jack Sparrow")
             (age-option "Blackbeard")
             (age-option "Hector Barbossa"))
;; #library_design.option.Some{:v 56.666668}

((alift avg) (age-option "Jack Sparrow")
             (age-option "Davy Jones")
             (age-option "Hector Barbossa"))
;; #library_design.option.None{}

As with Functors, we can take the code as it is, and simply replace the underlying abstraction, preventing repetition once again:

((alift avg) (i/future (some-> (pirate-by-name "Jack Sparrow") age))
             (i/future (some-> (pirate-by-name "Blackbeard") age))
             (i/future (some-> (pirate-by-name "Hector Barbossa") age)))
;; #<Future@17b1be96: #<Success@16577601: 56.666668>>