Book Image

Hands-On Reactive Programming with Clojure - Second Edition

By : Konrad Szydlo, Leonardo Borges
Book Image

Hands-On Reactive Programming with Clojure - Second Edition

By: Konrad Szydlo, Leonardo Borges

Overview of this book

Reactive Programming is central to many concurrent systems, and can help make the process of developing highly concurrent, event-driven, and asynchronous applications simpler and less error-prone. This book will allow you to explore Reactive Programming in Clojure 1.9 and help you get to grips with some of its new features such as transducers, reader conditionals, additional string functions, direct linking, and socket servers. Hands-On Reactive Programming with Clojure starts by introducing you to Functional Reactive Programming (FRP) and its formulations, as well as showing you how it inspired Compositional Event Systems (CES). It then guides you in understanding Reactive Programming as well as learning how to develop your ability to work with time-varying values thanks to examples of reactive applications implemented in different frameworks. You'll also gain insight into some interesting Reactive design patterns such as the simple component, circuit breaker, request-response, and multiple-master replication. Finally, the book introduces microservices-based architecture in Clojure and closes with examples of unit testing frameworks. By the end of the book, you will have gained all the knowledge you need to create applications using different Reactive Programming approaches.
Table of Contents (15 chapters)

A bit of history

Before we talk about what Reactive Programming is, it is important to understand how other relevant programming paradigms influenced how we develop software. This will also help us understand the motivations behind Reactive Programming.

With a few exceptions, most of us will have been taught imperative programming languages such as C and Pascal or object-oriented languages such as Java and C++; either self-taught or at school/university

In both cases, the imperative programming paradigm of which object-oriented languages are a part dictates that we write programs as a series of statements that modify program state.

To understand what this means, let's look at a short program written in pseudocode that calculates the sum and the mean value of a list of numbers:

numbers := [1, 2, 3, 4, 5, 6] 
sum := 0 
for each number in numbers 
  sum := sum + number 
end 
mean := sum / count(numbers)
The mean value is the average of the numbers in the list, which is obtained by dividing the sum by the number of elements.

First, we create a new array of integers, called numbers, with numbers from 1 to 6, inclusive. Then, we initialize sum to 0. Next, we iterate over the array of integers, one at a time, adding the value of each number to sum.

Lastly, we calculate and assign the average of the numbers in the list to the mean local variable. This concludes the program logic.

This program would print 21 for the sum and 3 for the mean, if executed.

Though a simple example, it highlights its imperative style: we set up an application state, sum, and then explicitly tell the computer how to modify that state in order to calculate the result.

Dataflow programming

The previous example has an interesting property: the value of mean clearly has a dependency on the contents of sum.

Dataflow programming makes this relationship explicit. It models applications as a dependency graph through which data flows from operation to operation, and as values change, these changes are propagated to its dependencies.

Historically, dataflow programming has been supported by custom-built languages such as Lucid and BLODI, and as such, have been leaving other general-purpose programming languages out.

Let's see how this new insight would impact our previous example. We know that once the last line gets executed, the value of mean is assigned and won't change unless we explicitly reassign the variable.

However, let's imagine for a second that the pseudo-language we used earlier does support dataflow programming. In that case, assigning mean to an expression that refers to both sum and count, such as sum / count(numbers), would be enough to create the directed dependency graph that's shown in the following diagram:

Note that a direct side effect of this relationship is that an implicit dependency from sum to numbers is also created. This means that if the numbers change, the change is propagated through the graph, first updating sum and then, finally, updating mean.

This is where Reactive Programming comes in. This paradigm builds on dataflow programming and change propagation to bring this style of programming to languages that don't have native support for it.

For imperative programming languages, Reactive Programming can be made available via libraries or language extensions. We don't cover this approach in this book, but should the reader want more information on this subject, please refer to dc-lib (see https://code.google.com/p/dc-lib/) for an example. It is a framework that adds Reactive Programming support to C++ via dataflow constraints.

Object-oriented Reactive Programming

When designing interactive applications such as desktop Graphical User Interfaces (GUIs), we are essentially using an object-oriented approach to Reactive Programming. We will build a simple calculator application to demonstrate this style.

Clojure isn't an object-oriented language, but we will be interacting with parts of the Java API to build user interfaces that were developed in an OO paradigm, hence the title of this section.

Let's start by creating a new Leiningen project from the command line:

lein new calculator  

This will create a directory called calculator in the current folder. Next, open the project.clj file in your favorite text editor and add a dependency on seesaw, a Clojure library for working with Java Swing:

(defproject calculator "0.1.0-SNAPSHOT" 
  :description "FIXME: write description" 
  :url "http://example.com/FIXME" 
  :license {:name "Eclipse Public License" 
            :url "http://www.eclipse.org/legal/epl-v10.html"} 
  :dependencies [[org.clojure/clojure "1.8.0"] 
                 [seesaw "1.5.0"]])  

At the time of writing this book, the latest seesaw version that's available is 1.5.0.

Next, in the src/calculator/core.clj file, we'll start by requiring the seesaw library and creating the visual components we'll be using:

(ns calculator.core 
  (:require [seesaw.core :refer :all])) 
 
(native!) 
 
(def main-frame (frame :title "Calculator" :on-close :exit)) 
 
(def field-x (text "1")) 
(def field-y (text "2")) 
 
(def result-label (label "Type numbers in the boxes to add them up!")) 

The preceding snippet creates a window with the title Calculator, which ends the program when closed. We also created two text input fields, field-x and field-y, as well as a label that will be used to display the results, aptly named result-label.

We would like the label to be updated automatically as soon as a user types a new number into any of the input fields. The following code does exactly that:

(defn update-sum [e] 
  (try 
    (text! result-label 
         (str "Sum is " (+ (Integer/parseInt (text field-x)) 
                           (Integer/parseInt (text field-y))))) 
    (catch Exception e 
      (println "Error parsing input.")))) 
 
(listen field-x :key-released update-sum) 
(listen field-y :key-released update-sum) 

The first function, update-sum, is our event handler. It sets the text of result-label to the sum of the values in field-x and field-y. We use try/catch here as a really basic way to handle errors, since the key that's being pressed might not have been a number.
We then add the event handler to the :key-released event of both input fields.

In real applications, we never want a catch block such as the previous one. This is considered bad style, and the catch block should do something more useful, such as logging an exception, firing a notification, or resuming the application if possible.

We are almost done. All we need to do now is add the components we have created so far to our main-frame and, finally, display it as follows:

(config! main-frame :content 
         (border-panel 
          :north (horizontal-panel :items [field-x field-y]) 
          :center result-label 
          :border 5)) 
 
(defn -main [& args] 
  (-> main-frame pack! show!)) 

Now, we can save the file and run the program from the command line in the project's root directory:

lein run -m calculator.core  

You should see something like the following screenshot:

Experiment by typing some numbers into either or both text input fields and watch how the value of the label changes automatically, displaying the sum of both numbers.

Congratulations! You have just created your first reactive application!

As alluded to previously, this application is reactive because the value of the result label reacts to user input and is updated automatically. However, this isn't the whole story—it lacks in composability and requires us to specify the how, not the what, of what we're trying to achieve.

As familiar as this style of programming may be, making applications reactive this way isn't always ideal.

Given the previous discussions, we noticed that we still had to be fairly explicit in setting up the relationships between the various components, as evidenced by having to write a custom handler and binding it to both input fields.

As we will see throughout the rest of this book, there is a much better way to handle similar scenarios.

The most widely used reactive program

Both examples in the previous section will feel familiar to some readers. If we call the input text fields cells and the result label's handler formula, we now have the nomenclature that's used in modern spreadsheet applications such as Microsoft Excel.

The term Reactive Programming has only been in use in recent years, but the idea of a reactive application isn't new. The first electronic spreadsheet dates back to 1969, when Rene Pardo and Remy Landau, then recent graduates from Harvard University, created LANguage for Programming Arrays at Random (LANPAR)[1].

It was invented to solve a problem that Bell Canada and AT&T had at the time: their budgeting forms had 2,000 cells that, when modified, forced a software rewrite, taking anywhere from six months to two years to complete.

To this day, electronic spreadsheets remain a powerful and useful tool for professionals in various fields.

The Observer design pattern

Another similarity that the keen reader may have noticed is with the Observer design pattern. It is mainly used in object-oriented applications as a way for objects to communicate with each other without having any knowledge of who depends on its changes.

In Clojure, a simple version of the Observer pattern can be implemented using watches:

(def numbers (atom [])) 
 
(defn adder [key ref old-state new-state] 
  (print "Current sum is " (reduce + new-state))) 
 
(add-watch numbers :adder adder) 

We will start by creating our program state, which in this case is an atom holding an empty vector. Next, we will create a watch function that knows how to sum all numbers in numbers. Finally, we will add our watch function to the numbers atom under the :adder key (useful for removing watches).

The adder key conforms with the API contract required by add-watch and receives four arguments. In this example, we only care about new-state.

Now, whenever we update the value of numbers, its watch will be executed, as demonstrated in the following code:

(swap! numbers conj 1) 
;; Current sum is  1 
 
(swap! numbers conj 2) 
;; Current sum is  3 
 
(swap! numbers conj 7) 
;; Current sum is  10 

The highlighted lines indicate the result that is printed on the screen each time we update the atom.

Though useful, the Observer pattern still requires some amount of work in setting up the dependencies and the required program state, in addition to being hard to compose.

That being said, this pattern has been extended and is at the core of one of the Reactive Programming frameworks we will look at later in this book, Microsoft's Reactive Extensions (Rx).

Functional Reactive Programming

Just like Reactive Programming, Functional Reactive Programming (FRP) has unfortunately become an overloaded term.

Frameworks such as RxJava (see https://github.com/ReactiveX/RxJava), ReactiveCocoa (see https://github.com/ReactiveCocoa/ReactiveCocoa), and Bacon.js (see https://baconjs.github.io/) have become extremely popular in recent years and have positioned themselves incorrectly as FRP libraries. This has led to the confusion surrounding the terminology.

As we will see, these frameworks do not implement FRP, but rather are inspired by it.

In the interest of using the correct terminology, as well as understanding what inspired by FRP means, we will have a brief look at the different formulations of FRP.

Higher-order FRP

Higher-order FRP refers to the original research on FRP that was developed by Conal Elliott and Paul Hudak in their paper Functional Reactive Animation[2] from 1997. This paper presents Fran, a domain-specific language embedded in Haskell for creating reactive animations. It has since been implemented in several languages as a library as well as purpose-built reactive languages.

If you recall the calculator example we created a few pages ago, we can see how that style of Reactive Programming requires us to manage state explicitly by directly reading and writing from/to the input fields. As Clojure developers, we know that avoiding state and mutable data is a good principle to keep in mind when building software. This principle is at the core of Functional Programming:

(->> [1 2 3 4 5 6] 
     (map inc) 
     (filter even?) 
     (reduce +)) 
;; 12 

This short program increments all of the elements in the original list by one filters all even numbers, and adds them up using reduce.

Note how we didn't have to explicitly manage local state through each step of the computation.

Different from imperative programming, we focus on what we want to do, for example, iteration, and not how we want it to be done, for example, using a for loop. This is why the implementation matches our description of the program closely. This is known as declarative programming.

FRP brings the same philosophy to Reactive Programming. As the Haskell programming language wiki on the subject has wisely put it,

"FRP is about handling time-varying values like they were regular values."

Put another way, FRP is a declarative way of modeling systems that respond to input over time.

Both statements touch on the concept of time. We'll be exploring that in the next section, where we introduce the key abstractions provided by FRP: signals (or behaviors) and events.