Functional Reactive Programming (FRP)

Functional programming avoids immutability and side-effects. In some circumstances, the application should react to dynamic value/data changes. For instance, we may need to change the user interface of an iOS application to reflect received data from a backend or database system. How would we do this without states and mutable values?

Imperative programming captures these dynamic values only indirectly, through state and mutations. The complete history (past, present, and future) has no first-class representation. Moreover, only discretely-evolving values can be (indirectly) captured as the imperative paradigm is temporally discrete.

FRP provides a way to handle dynamic value changes while still retaining the FP style. FRP, as its name suggests, is a combination of FP and reactive programming. Reactive programming makes it possible to deal with certain data types that represent values over time. These data types are called time flow or event streams in different functional programming languages. Computations that involve these changing-over-time/evolving values will themselves have values that change over time. FRP captures these evolving values directly and has no difficulty with continuously evolving values.

In addition, FRP can be presented as the following set of principles/rules:

  • Data types or dynamic/evolving over time values should be first-class citizens. We should be able to define, combine, and pass them to functions and return them from functions.
  • Data types should be built from a few primitives such as constant/static values and time with sequential and parallel combinations. The n behaviors are combined by applying an n-ary function to static values continuously over time.
  • To account for discrete phenomena, we should have additional event types, each of which has a stream (finite or infinite) of occurrences. Each occurrence has an associated time and value.
  • To come up with the compositional vocabulary out of which all behaviors and events can be built, play with some examples. Keep deconstructing into pieces that are more general/simple.
  • We should be able to compose the whole model, using the technique of denotational semantics:
    • Each type has a corresponding simple and precise mathematical type of meaning
    • Each primitive and operator has a simple and precise meaning as a function of the meanings of the constituents

Building blocks of FRP

It is important to understand FRP building blocks to be able to understand FRP. The following sections explain these building blocks with one of the great FRP libraries for the Cocoa framework called ReactiveCocoa developed by GitHub. ReactiveCocoa was developed for Objective-C and, as of version 3.0, all major feature development is concentrated on the Swift API.

Signals

Signals are event streams that send values over time that are already in progress. We can imagine them as pipes that send values without knowing about the previous values that they sent or future values that they are going to send. Signals can be composed, combined, and chained declaratively. Signals can unify all Cocoa common patterns for asynchrony and event handling:

  • Delegate methods
  • Callback blocks
  • Notifications
  • Control actions and responder chain events
  • Future and Promises
  • Key-value observing (KVO)

As all of these mechanisms can be represented in the same way, it is easy to declaratively chain and combine them together.

ReactiveCocoa represents signals as Signal. Signals can be used to represent notifications, user input, and so on. As work is performed or data is received, events are sent on the signal, which pushes them out to any observers. All observers see the events at the same time.

Users must observe a signal in order to access its events. Observing a signal does not trigger any side-effects. In other words, signals are entirely producer-driven and push-based, and observers cannot have any effect on the signal's lifetime. While observing a signal, the user can only evaluate the events in the same order as they are sent on the signal. There is no random access to values of a signal.

Signals can be manipulated by applying the following operations:

  • map, filter, and reduce to manipulate a single signal
  • zip to manipulate multiple signals at once

These operations can be applied only on the next events of a signal.

The lifetime of a signal may consist of a various number of next events, followed by one terminating event, which may be any one of the following:

  • Failed
  • Completed
  • Interrupted

Terminating events are not included in the signal's values and they should be handled specially.

Pipes

A signal that can be manually controlled is called pipe. In ReactiveCocoa, we can create a pipe by calling Signal.pipe().

The pipe method returns signal and observer. The signal can be controlled by sending events to the observer.

Signal producers

A signal producer creates signals and performs side-effects. SignalProducer can be used to represent operations or tasks such as network requests, where each invocation of start() will create a new underlying operation and allow the caller to observe the result. Unlike a signal, no work is started (and thus no events are generated) until an observer is attached, and the work is restarted for each additional observer.

Starting a signal producer returns a disposable that can be used to interrupt/cancel the work associated with the produced signal.

Signal producers can also be manipulated via operations such as map, filter, and reduce. Every signal operation can be lifted to operate upon signal producers instead, using the lift method.

Buffers

A buffer is an optionally bounded queue for events. A buffer replays these events when new signals are created from SignalProducer. A buffer is created by calling SignalProducer.buffer(). Similar to pipe, the method returns observer. Events sent to this observer will be added to the queue. If the buffer is already at capacity when a new value arrives, the oldest value will be dropped to make room for it.

Observers

An observer is anything that observes or is capable of observing events from a signal. Observers can be implicitly created using the callback-based versions of the Signal.observe() or SignalProducer.start() methods.

Actions

An action will do some work when executed with an input. Actions are useful in performing side-effecting work upon user interaction, such as when a button is clicked. Actions can also be automatically disabled based on a property, and this disabled state can be represented in a user interface by disabling any controls associated with the action.

Properties

A property stores a value and notifies observers about future changes to that value. The current value of a property can be obtained from the value getter. The producer getter returns a signal producer that will send the property's current value, followed by all changes over time.

Disposables

A disposable is a mechanism for memory management and cancellation. When starting a signal producer, a disposable will be returned. This disposable can be used by the caller to cancel the work that has been started, clean up all temporary resources, and then send a final Interrupted event with regard to the particular signal that was created.

Schedulers

A scheduler is a serial execution queue to perform work or deliver results upon. Signals and signal producers can be ordered to deliver events on a specific scheduler. Signal producers can additionally be ordered to start their work on a specific scheduler.

Schedulers are similar to the Grand Central Dispatch (GCD) queues, but schedulers support cancellation via disposables and always execute serially. With the exception of ImmediateScheduler, schedulers do not offer synchronous execution. This helps avoid deadlocks and encourages the use of signal and signal producer operations instead of blocking work.

Schedulers are also somewhat similar to NSOperationQueue, but schedulers do not allow tasks to be reordered or depend on one another.

An example

Let's suppose that we have an outlet and we want to observe its changes:

@IBOutlet weak var textFieldUserName: UITextField!

We can create SignalProducer as follows:

let userNameSignalProducer = 
  textFieldUserName.rac_textSignal().toSignalProducer.map { 
  text in text as! String }

The rac_textSignal method is a ReactiveCocoa extension for UITextField that can be used to create the signal producer.

Then, we can start our SignalProducer as follows:

userNameSignalProducer.startWithNext { results in 
      print("User name:(results)") 
}

This will print any changes in our textField to the console.

Also, we can execute operations such as map, flatMap, filter, and reduce on this signal producer, which we covered in Chapter 6, Map, Filter, and Reduce.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset