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

The semantics of map


We will get started by taking a look at one of the most used operations in these abstractions: map.

We've been using map for a long time in order to transform sequences. Thus, instead of creating a new function name for each new abstraction, library designers simply abstract the map operation over its own container type.

Imagine the mess we would end up in if we had functions such as transform-observable, transform-channel, combine-futures, and so on.

Thankfully, this is not the case. The semantics of map are well understood to the point that even if a developer hasn't used a specific library before, he will almost always assume that map will apply a function to the value(s) contained within whatever abstraction the library provides.

Let's look at three examples we encountered in this book. We will create a new leiningen project in which to experiment with the contents of this appendix:

$ lein new library-design

Next, let's add a few dependencies to our project.clj file:

...
:dependencies [[org.clojure/clojure "1.6.0"]
               [com.leonardoborges/imminent "0.1.0"]
               [com.netflix.rxjava/rxjava-clojure "0.20.7"]
               [org.clojure/core.async "0.1.346.0-17112a-alpha"]
               [uncomplicate/fluokitten "0.3.0"]]
...

Don't worry about the last dependency—we'll get to it later on.

Now, start an REPL session so that we can follow along:

$ lein repl

Then, enter the following into your REPL:

(require '[imminent.core :as i]
         '[rx.lang.clojure.core :as rx]
         '[clojure.core.async :as async])

(def  repl-out *out*)
(defn prn-to-repl [& args]
  (binding [*out* repl-out]
    (apply prn args)))
(-> (i/const-future 31)
    (i/map #(* % 2))
    (i/on-success #(prn-to-repl (str "Value: " %))))


(as-> (rx/return 31) obs
      (rx/map #(* % 2) obs)
      (rx/subscribe obs #(prn-to-repl (str "Value: " %))))

(def c        (chan))
(def mapped-c (async/map< #(* % 2) c))

(async/go (async/>! c 31))
(async/go (prn-to-repl (str "Value: " (async/<! mapped-c))))

"Value: 62"
"Value: 62"
"Value: 62"

The three examples—using imminent, RxClojure, and core.async, respectively—look remarkably similar. They all follow a simple recipe:

  1. Put the number 31 inside their respective abstraction.

  2. Double that number by mapping a function over the abstraction.

  3. Print its result to the REPL.

As expected, this outputs the value 62 three times to the screen.

It would seem map performs the same abstract steps in all three cases: it applies the provided function, puts the resulting value in a fresh new container, and returns it. We could continue generalizing, but we would just be rediscovering an abstraction that already exists: Functors.

Functors

Functors are the first abstraction we will look at and they are rather simple: they define a single operation called fmap. In Clojure, Functors can be represented using protocols and are used for containers that can be mapped over. Such containers include, but are not limited to, lists, Futures, Observables, and channels.

Tip

The Algebra in the title of this Appendix refers to Abstract Algebra, a branch of Mathematics that studies algebraic structures. An algebraic structure is, to put it simply, a set with one or more operations defined on it.

As an example, consider Semigroups, which is one such algebraic structure. It is defined to be a set of elements together with an operation that combines any two elements of this set. Therefore, the set of positive integers together with the addition operation form a Semigroup.

Another tool used for studying algebraic structures is called Category Theory, of which Functors are part of.

We won't delve too much into the theory behind all this, as there are plenty of books [9][10] available on the subject. It was, however, a necessary detour to explain the title used in this appendix.

Does this mean all of these abstractions implement a Functor protocol? Unfortunately, this is not the case. As Clojure is a dynamic language and it didn't have protocols built in—they were added in version 1.2 of the language—these frameworks tend to implement their own version of the map function, which doesn't belong to any protocol in particular.

The only exception is imminent, which implements the protocols included in fluokitten, a Clojure library providing concepts from Category theory such as Functors.

This is a simplified version of the Functor protocol found in fluokitten:

(defprotocol Functor
  (fmap [fv g]))

As mentioned previously, Functors define a single operation. fmap applies the function g to whatever value is inside the container, Functor, fv.

However, implementing this protocol does not guarantee that we have actually implemented a Functor. This is because, in addition to implementing the protocol, Functors are also required to obey a couple of laws, which we will examine briefly.

The identity law is as follows:

(= (fmap a-functor identity)
   (identity a-functor))

The preceding code is all we need to verify this law. It simply says that mapping the identity function over a-functor is the same as simply applying the identity function to the Functor itself.

The composition law is as follows:

(= (fmap a-functor (comp f g))
   (fmap (fmap a-functor g) f))

The composition law, in turn, says that if we compose two arbitrary functions f and g, take the resulting function and apply that to a-functor, that is the same as mapping g over the Functor and then mapping f over the resulting Functor.

No amount of text will be able to replace practical examples, so we will implement our own Functor, which we will call Option. We will then revisit the laws to ensure we have respected them.

The Option Functor

As Tony Hoare once put it, null references are his one billion dollar mistake (http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare). Regardless of background, you no doubt will have encountered versions of the dreadful NullPointerException. This usually happens when we try to call a method on an object reference that is null.

Clojure embraces null values due to its interoperability with Java, its host language, but it provides improved support for dealing with them.

The core library is packed with functions that do the right thing if passed a nil value—Clojure's version of Java's null. For instance, how many elements are there in a nil sequence?

(count nil) ;; 0

Thanks to conscious design decisions regarding nil, we can, for the most part, afford not worry about it. For all other cases, the Option Functor might be of some help.

The remaining of the examples in this appendix should be in a file called option.clj under library-design/src/library_design/. You're welcome to try this in the REPL as well.

Let's start our next example by adding the namespace declaration as well as the data we will be working with:

(ns library-design.option
  (:require [uncomplicate.fluokitten.protocols :as fkp]
            [uncomplicate.fluokitten.core :as fkc]
            [uncomplicate.fluokitten.jvm :as fkj]
            [imminent.core :as I]))

(def pirates [{:name "Jack Sparrow"    :born 1700 :died 1740 :ship "Black Pearl"}
              {:name "Blackbeard"      :born 1680 :died 1750 :ship "Queen Anne's Revenge"}
              {:name "Hector Barbossa" :born 1680 :died 1740 :ship nil}])

(defn pirate-by-name [name]
  (->> pirates
       (filter #(= name (:name %)))
       first))

(defn age [{:keys [born died]}]
  (- died born))

As a Pirates of the Caribbean fan, I thought it would be interesting to play with pirates for this example. Let's say we would like to calculate Jack Sparrow's age. Given the data and functions we just covered, this is a simple task:

  (-> (pirate-by-name "Jack Sparrow")
      age) ;; 40

However, what if we would like to know Davy Jones' age? We don't actually have any data for this pirate, so if we run our program again, this is what we'll get:

(-> (pirate-by-name "Davy Jones")
      age) ;; NullPointerException   clojure.lang.Numbers.ops (Numbers.java:961)

There it is. The dreadful NullPointerException. This happens because in the implementation of the age function, we end up trying to subtract two nil values, which is incorrect. As you might have guessed, we will attempt to fix this by using the Option Functor.

Traditionally, Option is implemented as an algebraic data type, more specifically a sum type with two variants: Some and None. These variants are used to identify whether a value is present or not without using nils. You can think of both Some and None as subtypes of Option.

In Clojure, we will represent them using records:

(defrecord Some [v])

(defrecord None [])

(defn option [v]
  (if v
    (Some. v)
    (None.)))

As we can see, Some can contain a single value whereas None contains nothing. It's simply a marker indicating the absence of content. We have also created a helper function called option, which creates the appropriate record depending on whether its argument is nil or not.

The next step is to extend the Functor protocol to both records:

(extend-protocol fkp/Functor
  Some
  (fmap [f g]
    (Some. (g (:v f))))
  None
  (fmap [_ _]
    (None.)))

Here's where the semantic meaning of the Option Functor becomes apparent: as Some contains a value, its implementation of fmap simply applies the function g to the value inside the Functor f, which is of type Some. Finally, we put the result inside a new Some record.

Now what does it mean to map a function over a None? You probably guessed that it doesn't really make sense—the None record holds no values. The only thing we can do is return another None. As we will see shortly, this gives the Option Functor a short-circuiting semantic.

Tip

In the fmap implementation of None, we could have returned a reference to this instead of a new record instance. I've not done so simply to make it clear that we need to return an instance of None.

Now that we've implemented the Functor protocol, we can try it out:

(->> (option (pirate-by-name "Jack Sparrow"))
     (fkc/fmap age)) ;; #library_design.option.Some{:v 40}

(->> (option (pirate-by-name "Davy Jones"))
     (fkc/fmap age)) ;; #library_design.option.None{}

The first example shouldn't hold any surprises. We convert the pirate map we get from calling pirate-by-name into an option, and then fmap the age function over it.

The second example is the interesting one. As stated previously, we have no data about Davy Jones. However, mapping age over it does not throw an exception any longer, instead returning None.

This might seem like a small benefit, but the bottom line is that the Option Functor makes it safe to chain operations together:

(->> (option (pirate-by-name "Jack Sparrow"))
     (fkc/fmap age)
     (fkc/fmap inc)
     (fkc/fmap #(* 2 %))) ;; #library_design.option.Some{:v 82}

(->> (option (pirate-by-name "Davy Jones"))
     (fkc/fmap age)
     (fkc/fmap inc)
     (fkc/fmap #(* 2 %))) ;; #library_design.option.None{}

At this point, some readers might be thinking about the some-> macro—introduced in Clojure 1.5—and how it effectively achieves the same result as the Option Functor. This intuition is correct as demonstrated as follows:

(some-> (pirate-by-name "Davy Jones")
        age
        inc
        (* 2)) ;; nil

The some-> macro threads the result of the first expression through the first form if it is not nil. Then, if the result of that expression isn't nil, it threads it through the next form and so on. As soon as any of the expressions evaluates to nil, some-> short-circuits and returns nil immediately.

That being said, Functor is a much more general concept, so as long as we are working with this concept, our code doesn't need to change as we are operating at a higher level of abstraction:

(->> (i/future (pirate-by-name "Jack Sparrow"))
     (fkc/fmap age)
     (fkc/fmap inc)
     (fkc/fmap #(* 2 %))) ;; #<Future@30518bfc: #<Success@39bd662c: 82>>

In the preceding example, even though we are working with a fundamentally different tool—futures—the code using the result did not have to change. This is only possible because both Options and futures are Functors and implement the same protocol provided by fluokitten. We have gained composability and simplicity as we can use the same API to work with various different abstractions.

Speaking of composability, this property is guaranteed by the second law of Functors. Let's see if our Option Functor respects this and the first—the identity—laws:

;; Identity
(= (fkc/fmap identity (option 1))
   (identity (option 1))) ;; true


;; Composition
(= (fkc/fmap (comp identity inc) (option 1))
   (fkc/fmap identity (fkc/fmap inc (option 1)))) ;; true

And we're done, our Option Functor is a lawful citizen. The remaining two abstractions also come paired with their own laws. We will not cover the laws in this section, but I encourage the reader to read about them (http://www.leonardoborges.com/writings/2012/11/30/monads-in-small-bites-part-i-functors/).