Reactive Programming - An Introduction With Swift - Part 1

This post is part of a series of articles on Reactive Programming Part 1 | Part 2 | ...

Prerequisites

This is a very high level and basic intro to reactive programming. From a reader perspective, it assumes:

  • You are at an intermediate level in programming (you are not an absolute beginner and know the difference between a variable and a function and a closure)
  • You are comfortable with Swift (you know what extensions are and how to constrain them at your will; although 80% of the code should be easy to follow even if you don't know what Swift is)
  • You know something about functional programming (just enough though...)
  • Monads do not scare you (don't worry, you won't find them here)

Introduction

Reactive programming is a paradigm to writing code in a way that concentrates data flow and propagating change across variables.

Well...

Reactive Variables

Consider we have two integer variables, a and b.

If we state that the value of b is one plus a, we could write this out as:

var a = 42
var b = a + 1

Seems straightforward. Now what if we modify our initial statement to state that the value of b is always a plus one?

var a = 42
var b = a + 1
a = 100
b // still 43 :-/

Specifying this sort of constraint is not directly possible in Swift at a language level. We could imagine we would be able to write code similar to the following:

var a = 42
var b <= a + 1 // special operator '<='
a = 100
print(b) // prints 101 magically

In the above example, <= data-preserve-html-node="true" is a magical operator that allows us to specify that the value of b depends on the value of a. Specifying this dependency allows us to change the value of a later on, and we can be assured that the value of b will get updated accordingly, and will always be a plus one.

So how can we accomplish this style of programming with Swift?

Observing Changes

The fundamental thing to note is that changes to a should be notified to external interested parties, and these notifications should be capable of being observed somehow, and all dependent variables should be notified of this change, so that they can react accordingly. This can be accomplished using the Observer Pattern.

NOTE The terminology out there is quite varied - Observer, Listener / Producer, Signal, Event / Subscribe, Bind, Listen and many more. There are slight nuances about how these differ.

For this article, we use the term bindto denote the act of an observer subscribing to a change produced by an observable.

Consider a type Observable<T> that wraps its type T as follows:

class Observable<T> {

    // a list of observers that should be notified
    // of changes to the underlying value
    var observers = [(T)->()]()

    init(_ value: T) {
        self.value = value
    }

    var value: T {
        didSet {
            // whenever the value changes notify
            // all observers with the updated value
            observers.forEach { $0(value) }
        }
    }

    // add a new observer binding
    func bind(_ f: @escaping (T)->()) {
        observers.append(f)
    }
}

With Observable<T> we can now write the following:

var a = Observable<Int>(10)
a.bind { print("a:", $0) }

a.value = 1001         // will print 'a: 1001'
a.value = 7         // will print 'a: 7'
a.value = 233         // will print 'a: 233'

This is the observer pattern at its most basic level, changes to a value are being notified to its observers. Now, whenever the underlying of a.value changes, it will automatically print out the new value.

Alright, so what happens if we write code like:

var a = Observable<Int>.init(0)
var b = a.value + 1 // we just set b once

a.value = 100
print(b) // prints '1'

Well, nothing much, actually. We still do not have a mechanism to tell how b is always dependent on a. Let's try to bind a closure that updates b:

var a = Observable<Int>.init(0)
var b = a.value + 1 // as before, set initial value

a.bind { b = $0 + 1 } // binding such that b is updated to a + 1, when a changes

print(a.value, b) // prints 0, 1

a.value = 100
print(a.value, b) // prints 100, 101!

Notice that in the last two lines, we first update a.value to 100 and later we print b, its value is now 101 as expected. We now have a very very basic implementation of a reactive variable. We can do much better.

In the previous examples, we were dealing with integer values. What if we want the value of b to be a string representation of a instead. We could write this out as:

var a = Observable<Int>.init(0)
var b = "\(a.value)"

a.bind { b = "\($0)" }

A Practical Example: Reactive UI

To show a more practical use-case, consider we have a label UI element, and a variable name that is used to populate the .text property of the label. From a reactive context, we want the label to always display the value in the name variable.

The class for our demo label looks like:

class Label {
    var text: String = "" {
        didSet {
            print("will layout with text: \(text)")
        }
    }
    init() { } 
}

The label as such has no notion of reactive-ness.; we can extend the Label class to become reactive:

// Reactive acts as a container for a Base type
class Reactive<Base> where Base: AnyObject {
    weak var base: Base? = nil
    init(_ base: Base) { 
        self.base = base 
    }
}

// If the Base is a Label, add in a text function
// Notice the function signature: (String) -> ()
extension Reactive where Base: Label {
    func text(_ text: String) {
        base?.text = text
    }
}

// Extend Label to provide an instance of a Reactive container
extension Label {
    var rx: Reactive<Label> { return Reactive<Label>.init(self) }
}

With the above code in place, we can write something like:

var name = Observable<String>.init("")
var label = Label()

// binds name to the label's text
// this works because the bind function
// simply expects a parameter of type (String)->()
// which the text() function of the Reactive 
// container of a Label will match

name.bind(label.rx.text) 

// now, whenever we set the name variable
// the label will automatically update it's
// appearance

name.value = "John Doe" // label will print 'will layout with text: John Doe'

assert(name.value == label.text) // succeeds

Functional Extensions

Our basic Observable class seems to be capable of quite a feat at this moment. But we can do more. Time to look at some functional programming.

Consider the map function. When defined over a type T, map is a function that takes in as input another function of type (T)->U and returns U.

For an observable, we do something similar, except that the types get wrapped in an Observable.

extension Observable {

    // maps an Observable<T> to Observable<U>
    func map(_ f: @escaping (T) -> U) -> Observable<U> {

        // create a new observable
        // and bind chages to self to it

        var r = Observable<U>.init(f(value)) 
        bind { value in
            r.value = f(value)
        }

        return r
    }
}

Having a map allows us to write the following:

var a = Observable<Int>.init(0)
var b = a.map { $0 + 1 }

print(a.value, b.value) // prints 0, 1

a.value = 100
print(a.value, b.value) // prints 100, 101

Interesting isn't it? Both a and b are now Observables, and b's value depends on a.

Since we have a map function, let's see if we can pull in more functional methods. Let's implement filter and see what happens.

extension Observable {
    func filter(_ f: @escaping (T) -> Bool) -> Observable<T> {
        let r = Observable(self.value)
        bind { value in
            if f(value) {
                r.value = value
            }
        }
        return r
    }
}

And now with code like the following, we have a mechanism to filter out even numbers and print a message whenever the value of a changes.

var a = Observable<Int>.init(0)
a.filter { $0 % 2 == 0 }
 .map { "\($0) is even" }
 .bind { print($0) }

If you consider a to be a stream of values, the above three lines is able to filter, map and process that stream. To solidify this stream concept of an observable, you can easily write a function that can convert a stream of values to a group of values.

Consider the following:

extension Observable {

    // buffers values until a certain count is reached
    func buffer(count: Int) -> Observable<[T]> {
        let r = Observable<[T]>.init([])
        var b = [T]()
        bind { value in
            if b.count == count {
                r.value = b
                b = []
            }
            b.append(value)
        }
        return r
    }

}

And now, if we write:

var a = Observable<Int>(0)

// buffer values and then print them

a.buffer(count: 3)
 .bind { print($0) }

for i in 0..<10 {
    a.value = i
}

// prints:
// [0, 1, 2]
// [3, 4, 5]
// [6, 7, 8]

We have created a stream that buffers values from its input stream until a certain count (3 in this case) of them are received, and then outputs the buffered values.

Note that we wait until the current buffer is full before triggering the configured observers (printing out the buffer in this case).

Conclusion

So there you have it, a very short intro into high level aspects of reactive programming. Give a shout out at @benziahamed on Twitter.

Exercises

The design we have come up with until now has some limitations:

  1. In our first set of examples when we created b as an Observable, reading the value of b would always give us a.value + 1. However, there was nothing stopping us from write a line like b.value = 10000000, even when a.value was 10. This actually would break the constraint we set out initially. What fundamental design change would be needed to ensure this will not happen? What do you think are the benefits of removing implicit storage of state?

  2. The Observable<T> implementation outlined in this article requires initialization with a known value of T. Implement an Observable<T> that does not require this, and has no value property. The only way to get a value out of an Observable would then be to strictly observe it.

  3. Now implement flatMap on our modified version of Observable. Did you face any problems in creating this function? I know I said you won't encounter a monad here, but if you implemented flatMap, well, you just created your own monad type.

  4. Would implementing reduce on an Observable make sense?

  5. Design and implement a throttle function that works similar to buffer, except that it is dependent of a time threshold and not a count threshold. The timer starts the moment the input stream produces a value. As long as the timer is running, new values are buffered, and when the timer finishes, all buffered values are passed on to the downstream observers.

    • The signature would look like func threshold(interval: Double) -> Observable<[T]>.
    • You can use this function to detect double taps; double taps are taps that occur within a short interval of one another. If we had an observable over a tap that is generated: tap event observable => threshold 250ms => map to count => filter by count of two => bind double tap handler

In part 2 [ coming soon-ish :-) ], we will look at re-designing Observable and try to its limitations.

Further Material