Hands-On Reactive Programming with Clojure - Second Edition

4 (1 reviews total)
By Konrad Szydlo , Leonardo Borges
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. What is Reactive Programming?

About 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.

Publication date:
January 2019
Publisher
Packt
Pages
298
ISBN
9781789346138

 

What is Reactive Programming?

Reactive Programming is a term that's gaining more and more recognition in the IT community. As we will see in this chapter, it is not a new topic and has been sadly overlooked for many years. Recent advancements in hardware and software, combined with applications requiring more elaborate user interactions, have resulted in the concept of Reactive Programming being rediscovered by a wider audience. Reactive Programming is both an overloaded term and a broad topic. As such, this book will focus on a specific formulation of Reactive Programming called Compositional Event Systems (CES).

In this chapter, we will cover the following topics:

  • A working example of a reactive application in Clojure
  • The history of Reactive Programming
  • The most common terms in Reactive Programming
  • The most common applications of Reactive Programming
 

A taste of Reactive Programming

Before covering some history and background behind Reactive Programming and CES, I would like to open with a working, and hopefully compelling, example: an animation in which we draw a sine wave onto a web page.

The sine wave is simply a graphical representation of the sine function. It is a smooth, repetitive oscillation, and at the end of our animation, it will look like what's shown in the following screenshot:

This example will highlight how CES does the following:

  • Urges us to think about what we would like to do as opposed to how
  • Encourages small, specific abstractions that can be composed together
  • Produces terse and maintainable code that is easy to change

The core of this program boils down to four lines of ClojureScript:

(-> app-time
(.pipe (rx-take 700)) (.subscribe (fn [{:keys [x y]}] (fill-rect x y) "orange"))))

Simply by looking at this code, it is impossible to determine precisely what it does. However, do take the time to read and imagine what it could do.

First, we have a variable called app-time, which represents a sequence of time. The next line gives us the intuition that app-time is some sort of collection-like abstraction: we use rx-take to retrieve 700 numbers from it.

Finally, we have to .subscribe to this collection by passing it a callback. This callback will be called for each item in the sine wave, finally drawing out the given sine coordinates using the fill-rect function.

This is quite a bit to take in for now, as we haven't seen any other code yet, but that was the point of this little exercise: even though we know nothing about the specifics of this example, we are able to develop an intuition of how it might work.

Let's see what else is necessary to make this snippet animate a sine wave on our screen.

This example is built in ClojureScript and uses HTML5 canvas for rendering and RxJS (see https://github.com/reactivex/rxjs), a framework for Reactive Programming in JavaScript.

Before we start, keep in mind that we will not go into the details of these frameworks yet; that will happen in the next chapter. This means I'll be asking you to take quite a few things at face value, so don't worry if you don't immediately grasp how things work. The purpose of this example is to simply get us started in the world of Reactive Programming.

For this project, we will be using figwheel (see https://github.com/bhauman/lein-figwheel), a Leiningen template for ClojureScript that gives us a sample working application that we can use as a skeleton.

To create our new project, head over to the command line and invoke Leiningen as follows:

lein new figwheel sin-wave
cd sin-wave  

Next, we need to modify a couple of things in the generated project. Open up sin-wave/resources/index.html and update it to look like the following:

<!DOCTYPE html> 
<html> 
  <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <link href="css/style.css" rel="stylesheet" type="text/css">
<script src="js/rxjs.umd.js" type="text/javascript"></script></head>
<body>
<div id="app"></div>
<script src="js/compiled/sin_wave.js" type="text/javascript"></script>
<canvas id="myCanvas" width="650" height="200" style="border:1px solid #d3d3d3;"></canvas>
</body>
</html>

This simply ensures that we import both our application code and RxJS. We haven't downloaded RxJS yet, so let's do that now. Browse to https://unpkg.com/rxjs/bundles/rxjs.umd.js and save this file to sin-wave/resources/public/js. The previous snippets also add an HTML5 canvas element, onto which we will be drawing.

Now, open /src/cljs/sin_wave/core.cljs. This is where our application code will live. You can ignore what is currently there. Make sure you have a clean slate like the following one:

(ns sin-wave.core) 
 
(defn on-js-reload[]) 

Finally, go back to the command line under the sin-wave folder and start up the following application:

lein figwheel
Figwheel: Cutting some fruit, just a sec ...                            
Figwheel: Validating the configuration found in project.clj
Figwheel: Configuration Valid ;)
Figwheel: Starting server at http://0.0.0.0:3449
Figwheel: Watching build - dev
Figwheel: Cleaning build - dev
Compiling build :dev to "resources/public/js/compiled/sine_wave.js" from ["src"]...
Successfully compiled build :dev to "resources/public/js/compiled/sine_wave.js" in 16.12 seconds.
Figwheel: Starting CSS Watcher for paths ["resources/public/css"]
Launching ClojureScript REPL for build: dev
Figwheel Controls:
(stop-autobuild) ;; stops Figwheel autobuilder
(start-autobuild id ...) ;; starts autobuilder focused on optional ids
(switch-to-build id ...) ;; switches autobuilder to different build
(reset-autobuild) ;; stops, cleans, and starts autobuilder
(reload-config) ;; reloads build config and resets autobuild
(build-once id ...) ;; builds source one time
(clean-builds id ..) ;; deletes compiled cljs target files
(print-config id ...) ;; prints out build configurations
(fig-status) ;; displays current state of system
(figwheel.client/set-autoload false) ;; will turn autoloading off
(figwheel.client/set-repl-pprint false) ;; will turn pretty printing off
Switch REPL build focus:
:cljs/quit ;; allows you to switch REPL to another build
Docs: (doc function-name-here)
Exit: :cljs/quit
Results: Stored in vars *1, *2, *3, *e holds last exception object
Prompt will show when Figwheel connects to your application
[Rebel readline] Type :repl/help for online help info
ClojureScript 1.10.238
dev:cljs.user!{:conn 2}=>

Once the previous command finishes, the application will be available at http://localhost:3449/index.html, where you will find a blank, rectangular canvas. We are now ready to begin.

The main reason we are using the figwheel template for this example is that it performs hot-reloading of our application code via WebSockets. This means that we can have the browser and the editor side by side, and as we update our code, we will see the results immediately in the browser without having to reload the page.

To validate that this is working, open your web browser's console so that you can see the output of the scripts on the page. Then, add this to /src/cljs/sin_wave/core.cljs as follows:

(.log js/console "hello clojurescript") 

You should have seen the hello clojurescript message being printed to your browser's console. Make sure you have a working environment up to this point, as we will be relying on this workflow to interactively build our application.

It is also a good idea to make sure we clear the canvas every time figwheel reloads our file. This is simple enough to do by adding the following snippet to our core namespace:

(defn canvas []
(.getElementById js/document "myCanvas"))

(defn ctx []
(.getContext (canvas) "2d"))

The concept of time in RxJS

Now that we have a working environment, we can progress with our animation. It is probably a good idea to specify how often we would like to have a new animation frame.

This effectively means adding the concept of time to our application. You're free to play with different values, but let's start with a new frame every 10 milliseconds:

(def rx-interval js/rxjs.interval)
(def rx-take js/rxjs.operators.take)
(def rx-map js/rxjs.operators.map)
(def app-time (rx-interval 10))

As RxJS is a JavaScript library, we need to use ClojureScript's interoperability to call its functions. For convenience, we will bind the interval function of RxJS to a local var. We will use this approach throughout this book when appropriate.

Next, we will create an infinite stream of numbers, starting at 0, that will have a new element every 10 milliseconds. Let's make sure this is working as expected:

(-> app-time
(.pipe (rx-take 5)) (.subscribe (fn [n] (.log js/console n)))) ;; 0 ;; 1 ;; 2 ;; 3 ;; 4
I use the term stream very loosely here. It will be defined more precisely later in the book.

Remember that time is infinite, so we will use the take Rx operator in order to avoid indefinitely printing out numbers to the console.

Our next step is to calculate the 2D coordinate representing a segment of the sine wave we can draw. This will be given by the following functions:

(defn deg-to-rad [n] 
  (* (/ Math/PI 180) n)) 
 
(defn sine-coord [x] 
  (let [sin (Math/sin (deg-to-rad x)) 
        y   (- 100 (* sin 90))] 
    {:x   x 
     :y   y 
     :sin sin}))

(def sine-wave
(.pipe app-time (rx-map sine-coord)))

The sine-coord function takes an x point of our 2D canvas and calculates the y point based on the sine of x. The constants 100 and 90 simply control how tall and sharp the slope should be. As an example, try calculating the sine coordinate when x is 50:

(.log js/console (str (sine-coord 50))) 
;;{:x 50, :y 31.05600011929198, :sin 0.766044443118978} 

We will be using app-time as the source for the values of x. Creating the sine wave is now only a matter of combining both app-time and sine-coord:

(-> app-time 
    (.pipe (rx-take 5) )
    (.subscribe (fn [num] 
                  (.log js/console (sine-coord num))))) 
 
 ;; {:x 0, :y 100, :sin 0}  
 ;; {:x 1, :y 98.42928342064448, :sin 0.01745240643728351}  
 ;; {:x 2, :y 96.85904529677491, :sin 0.03489949670250097}  
 ;; {:x 3, :y 95.28976393813505, :sin 0.052335956242943835}  
 ;; {:x 4, :y 93.72191736302872, :sin 0.0697564737441253}  

This brings us to the original code snippet that piqued our interest, alongside a function to perform the actual drawing:

(defn fill-rect [x y colour]
(set! (.-fillStyle (ctx)) colour)
(.fillRect (ctx) x y 2 2)) (-> app-time (.pipe (rx-take 700)) (.subscribe (fn [num] (fill-rect x y "orange"))))

As this point, we can save the file again and watch as the sine wave we have just created gracefully appears on the screen.

More colors

One of the points this example sets out to illustrate is how thinking in terms of very simple abstractions and then building more complex ones on top of them make for code that is simpler to maintain and easier to modify.

As such, we will now update our animation to draw the sine wave in different colors. In this case, we would like to draw the wave in red if the sine of x is negative, and blue otherwise.

We already have the sine value coming through the sine-wave stream, so all we need to do is transform this stream into one that will give us the colors according to the preceding criteria:

(def colour (.pipe sine-wave 
(rx-map (fn [{:keys [sin]}] (if (< sin 0) "red" "blue")))))

The next step is to add the new stream into the main drawing loop—remember to comment the previous one so that we don't end up with multiple waves being drawn at the same time:

(-> (js/rxjs.zip sine-wave colour) 
    (.pipe (rx-take 700)) 
    (.subscribe (fn [[{:keys [x y]} colour]] 
                  (fill-rect x y colour)))) 

Once we save the file, we should see a new sine wave alternating between red and blue as the sine of x oscillates from -1 to 1.

Making it reactive

As fun as this has been so far, the animation we have created isn't really reactive. Sure, it does react to time itself, but that is the very nature of animation. As we will see later, Reactive Programming is called as such because programs react to external inputs such as a mouse or network events.

We will, therefore, update the animation so that the user is in control of when the color switch occurs: the wave will start off red and switch to blue when the user clicks anywhere within the canvas area. Further clicks will simply alternate between red and blue.

We start by creating infinite—as per the definition of app-time—streams for our color primitives as follows:

(def red  (.pipe app-time (rx-map (fn [_] "red")))) 
(def blue (.pipe app-time (rx-map (fn [_] "blue")))) 

On their own, red and blue aren't that interesting, as their values don't change. We can think of them as constant streams. They become a lot more interesting when combined with another infinite stream that cycles between them based on user input:

(def rx-concat     js/rxjs.concat) 
(def rx-defer      js/rxjs.defer) 
(def rx-from-event js/rxjs.fromEvent)
(def rx-takeUntil js/rxjs.operators.takeUntil) (def mouse-click (rx-from-event canvas "click")) (def cycle-colour (rx-concat (.pipe red (rx-takeUntil mouse-click)) (rx-defer #(rx-concat (.pipe blue (rx-takeUntil mouse-click)) cycle-colour))))

This is our most complex update so far. If you look closely, you will also notice that cycle-colour is a recursive stream; that is, it is defined in terms of itself.

When we first saw the code of this nature, we took a leap of faith in trying to understand what it does. After a quick read, however, we realized that cycle-colour closely follows how we might have talked about the problem: we will use red until a mouse click occurs, after which, we will use blue until another mouse click occurs. Then, we start the recursion.

The change to our animation loop is minimal:

(-> (js/rxjs.zip sine-wave cycle-colour) 
    (.pipe (rx-take 700))
    (.subscribe (fn [[{:keys [x y]} colour]] 
                  (fill-rect x y colour)))) 

The purpose of this book is to help you develop the instinct required to model problems in the way that's demonstrated here. After each chapter, more and more of this example will make sense. Additionally, a number of frameworks will be used both in ClojureScript and Clojure to give you a wide range of tools to choose from.

Before we move on to that, we must take a little detour and understand how we got here.

Exercise 1.1

Modify the previous example in such a way that the sine wave is drawn using all the colors of the rainbow. The drawing loop should look like the following:

(-> (js/rx.zip sine-wave rainbow-colours) 
    (pipe (rx-take 700))
    (.subscribe (fn [[{:keys [x y]} colour]] 
                  (fill-rect x y colour)))) 

Your task is to implement the rainbow-colours stream. As everything up until now has been very light on explanations, you might choose to come back to this exercise later, once we have covered more about CES.

The repeat, scan, and flatMap functions may be useful in solving this exercise. Be sure to consult RxJs' API at https://rxjs-dev.firebaseapp.com/guide/overview for more information.

 

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.

 

Signals and events

So far, we have been dealing with the idea of programs that react to user input. This is, of course, only a small subset of reactive systems, but is enough for the purposes of this discussion.

User input happens several times through the execution of a program: key presses, mouse drags, and clicks are but a few examples of how a user might interact with our system. All of these interactions happen over a period of time. FRP recognizes that time is an important aspect of reactive programs and makes it a first-class citizen through its abstractions.

Both signals (also called behaviors) and events are related to time. Signals represent continuous, time-varying values. Events, on the other hand, represent discrete occurrences at a given point in time.

For example, time is itself a signal. It varies continuously and indefinitely. On the other hand, a key press by a user is an event, that is, a discrete occurrence.

It is important to note, however, that the semantics of how a signal changes need not be continuous. Imagine a signal that represents the current (x,y) coordinates of your mouse pointer.

This signal is said to change discretely, as it depends on the user moving the mouse pointer—an event which isn't a continuous action.

 

Implementation challenges

Perhaps the most defining characteristic of classical FRP is the use of continuous time.

This means that FRP assumes that signals are changing all the time, even if their value is still the same, leading to needless recomputation. For example, the mouse position signal will trigger updates to the application dependency graph—like the one we saw previously for the mean program—even when the mouse is stationary.

Another problem is that classical FRP is synchronous by default: events are processed in order, one at a time. Harmless at first, this can cause delays, which would render an application unresponsive should an event take substantially longer to process.

Paul Hudak and others furthered research on higher-order FRP[7][8] to address these issues, but that came at the cost of expressivity.

The other formulations of FRP aim to overcome these implementation challenges.

Throughout the rest of this chapter, I'll be using the terms signals and behaviors interchangeably.

First-order FRP

The most well-known reactive language in this category is Elm (see http://elm-lang.org/), an FRP language that compiles to JavaScript. It was created by Evan Czaplicki and presented in his paper Elm: Concurrent FRP for Functional GUIs[3].

Elm makes some significant changes to higher-order FRP.

It abandons the idea of continuous time and is entirely event-driven. As a result, it solves the problem of needless recomputation, which was highlighted earlier. First-order FRP combines both behaviors and events into signals, which, in contrast to higher-order FRP, are discrete.

Additionally, first-order FRP allows the programmer to specify when the synchronous processing of events isn't necessary, preventing unnecessary processing delays.

Finally, Elm is a strict programming language, meaning that arguments to functions are evaluated eagerly. This is a conscious decision, as it prevents space and time leaks, which are possible in a lazy language such as Haskell.

In an FRP library such as Fran, which has been implemented in a lazy language, memory usage can grow unwieldy as computations are deferred to the absolutely last possible moment, therefore causing a space leak. These larger computations, which are accumulated over time due to laziness, can then cause unexpected delays when finally executed, thus causing time leaks.

Asynchronous data flow

Asynchronous data flow generally refers to frameworks such as Reactive Extensions (Rx), ReactiveCocoa, and Bacon.js. It is called as such as it completely eliminates synchronous updates.

These frameworks introduce the concept of Observable Sequences[4], sometimes called Event Streams.

This formulation of FRP has the advantage of not being confined to functional languages. Therefore, even imperative languages such as Java can take advantage of this style of programming.

Arguably, these frameworks were responsible for the confusion around FRP terminology. Conal Elliott, at some point, suggested the term CES (see https://twitter.com/conal/status/468875014461468677).

I have since adopted this terminology (see http://vimeo.com/100688924), as I believe it highlights two important factors:

  • A fundamental difference between CES and FRP: CES is entirely event-driven
  • CES is highly composable via combinators, taking inspiration from FRP

CES is the main focus of this book.

Arrowized FRP

This is the last formulation we will look at. Arrowized FRP[5] introduces two main differences over higher-order FRP: it uses signal functions instead of signals and is built on top of John Hughes' Arrow combinators[6].

It is mostly about a different way of structuring code and can be implemented as a library. As an example, Elm supports Arrowized FRP via its Automaton (see https://github.com/evancz/automaton) library.

The first draft of this chapter grouped the different formulations of FRP under the broad categories of Continuous and Discrete FRP. Thanks to Evan Czaplicki's excellent talk, Controlling Time and Space: understanding the many formulations of FRP (see https://www.youtube.com/watch?v=Agu6jipKfYw), I was able to borrow the more specific categories that are used here. These come in handy when discussing the different approaches to FRP.
 

Applications of FRP

In today's world, the different FRP formulations are being used in several problem spaces by professionals and big organizations alike. Throughout this book, we'll look at several examples of how CES can be applied. Some of these are interrelated, as most modern programs have several cross-cutting concerns, but we will only highlight two main areas.

Asynchronous programming and networking

GUIs are a great example of asynchronous programming. Once you open a web or a desktop application, it simply sits there, idle, waiting for user input.

This state is often called the event or main event loop. It is simply waiting for external stimuli, such as a key press, a mouse button click, new data from the network, or even a simple timer.

Each of these stimuli is associated with an event handler that gets called when one of these events happen, hence the asynchronous nature of GUI systems.

This is a style of programming that we have been used to for many years, but as business and user needs grow, these applications grow in complexity as well, and better abstractions are needed to handle the dependencies between all the components of an application.

Another great example that deals with managing complexity around network traffic is Netflix, which uses CES to provide a reactive API for their backend services.

Complex GUIs and animations

Games are, perhaps, the best example of complex user interfaces, as they have intricate requirements around user input and animations.

The Elm language we mentioned before is one of the most exciting efforts in building complex GUIs. Another example is Flapjax, also targeted at web applications, but it is provided as a JavaScript library that can be integrated with existing JavaScript code bases.

 

Summary

Reactive Programming is all about building responsive applications. There are several ways in which we can make our applications reactive. Some are old ideas: dataflow programming, electronic spreadsheets, and the Observer pattern are all examples. However, CES in particular has become popular in recent years.

CES aims to bring to Reactive Programming the declarative way of modeling problems that is at the core of Functional Programming. We should worry about what and not about how.

In the following chapters, we will learn how we can apply CES to our own programs.

 

Further reading

Here is a list of information you can refer to regarding what we have covered in this chapter:

  1. The World's First Electronic Spreadsheet, Rene Pardo and Remy Landau:
    http://www.renepardo.com/articles/spreadsheet.pdf
  2. Functional Reactive Animation, Conal Elliott and Paul Hudak:
    http://conal.net/papers/icfp97/icfp97.pdf
  3. Elm: Concurrent FRP for Functional GUIs, Evan Czaplicki:
    https://www.seas.harvard.edu/sites/default/files/files/archived/Czaplicki.pdf
  4. Subject/Observer is Dual to Iterator, Erik Meijer:
    http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf

  1. Functional Reactive Programming, Continued, Henrik Nilsson, Antony Courtney, and John Peterson:
    http://haskell.cs.yale.edu/wp-content/uploads/2011/02/workshop-02.pdf
  2. Generalising Monads to Arrows, John Hughes:
    http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf
  3. Real-Time FRP, Zhanyong Wan, Walid Taha, and Paul Hudak:
    http://haskell.cs.yale.edu/wp-content/uploads/2011/02/rt-frp.pdf
  4. Event-Driven FRP, Walid Taha, Zhanyong Wan, and Paul Hudak: https://www.researchgate.net/publication/2415013_Event-driven_FRP

About the Authors

  • Konrad Szydlo

    Konrad Szydlo is a psychology and computing graduate from Bournemouth University. He has worked with Clojure for the last 8 years. Since January 2016, he has worked as a software engineer and team leader at Retailic, responsible for building a website for the biggest royalty program in Poland. Prior to this, he worked as a developer with Sky, developing e-commerce and sports applications, where he used Ruby, Java, and PHP. He is also listed in the Top 75 Datomic developers on GitHub.

    Browse publications by this author
  • Leonardo Borges

    Leonardo Borges is a programming languages enthusiast who loves writing code, contributing to open source software, and speaking on subjects he feels strongly about. He has used Clojure professionally, both as a lead consultant at ThoughtWorks and as a development team lead at Atlassian, where he helped build real-time collaborative editing technology.

    Leonardo is currently the CTO for MODRON. Apart from this book, he contributed a couple of chapters to Clojure Cookbook, O'Reilly.

    Leonardo founded and currently runs the Sydney Clojure User Group in Australia. He also writes posts about software, with a focus on functional programming, on his website. When he isn't writing code, he enjoys riding motorcycles, weightlifting, and playing the guitar.

    Browse publications by this author

Latest Reviews

(1 reviews total)
Excellent book, it's really a hands-on content.

Recommended For You

Book Title
Unlock this book and the full library for FREE
Start free trial