Cookies
Diese Website verwendet Cookies und ähnliche Technologien für Analyse- und Marketingzwecke. Durch Auswahl von Akzeptieren stimmen Sie der Nutzung zu, alternativ können Sie die Nutzung auch ablehnen. Details zur Verwendung Ihrer Daten finden Sie in unseren Datenschutz­hinweisen, dort können Sie Ihre Einstellungen auch jederzeit anpassen.
Engineering

Functional Reactive Programming (FRP)

Minuten Lesezeit

Notice: This article is written in German.

Blog Post - Functional Reactive Programming (FRP)
Stephan Epping

Reactive Programming is a programming paradigm that experienced an increase in popularity during the last few years (Reactive Manifesto). However no common specification could be established until now. Therefore many terms are ambiguous and may be used differently in other resources. There are many different levels where reactive programming can be applied, e.g. system level, application level or class/method level. In this post I am going to cover the reactive concepts on the most basic level which programmers can apply to their daily work. Reactive programming shines where classical paradigms like imperative programming get messy and cannot express the important aspects very well, e.g. modeling data flows, scheduling asynchronous operations or real-time programming. The following example in pseudo-code shows the key difference between both paradigms:

a = 5, b = 2
c = a + b
print c #=> 7

a = 2
print c #=> 7
a := 5, b := 2
c := a + b
print c #=> 7

a := 2
print c #=> 4

In the imperative approach the variable c is bound to the result of the expression a + b; considering the values of a and b only at the specific point in time where the expression is being evaluated. Whereas in the reactive approach the property a is bound directly to the expression itself and subsequently to the properties a and b. This has the neat effect that a change of any the values a or b at any given point in time propagates to c. Properties can be seen as values that may change over time - sometimes called time-varying values. This behavior should sound very familiar to anybody who has worked with spreadsheet applications. Cells in a spreadsheet are nothing more than properties and cells can depend on each other using formulas/expressions/functions, e.g. A6=SUM(A1:A5).

Another way to look at these concepts is to compare typical interfaces to traverse through a collection of data in both domains. The imperative approach has a pull-based nature, like Enumerable, where the consumer is responsible to actively fetch the data from the producer. In contrast, reactive programming is more of a push-based concept, like Observable, where the producer is responsible for pushing new values to the consumer. In this case the consumer just has to deal with new values no matter what - it has to react.

trait Enumerable[+T] {
  def getEnumerator() : Enumerator[T]
}

trait Enumerator[-T] {
 def moveNext(): Boolean
 def current: T
}
trait Observable[+T] {
  def subscribe(o: Observer[T]): ()
}

trait Observer[-T] {
 def onCompleted(): ()
 def onError(error: Throwable): ()
 def onNext(value: T): ()
}

A little more thought has to be given into the theoretical background of asynchronous flows where latency plays a crucial role. But this would go beyond the scope of this post, therefore I highly recommend the talk of Erik Meijer at React 2014 for further details.

Functional reactive programming goes one step further than reactive programming and treats functions as first class citizens. Every property can be seen as function—a function over time where f(t) represents the value of the property at the given time t. Moreover, this allows composing properties in order to describe more complex behaviors; the same way mathematical functions can be composed. Interestingly, this follows the nature of functional programming, where data is immutable and side-effects are banned (or made explicit). In short: FRP is about time dependent relationships that establish responsiveness. Many of the existing libraries have their own implementations of these concepts and may name things differently. In the next paragraph I’ll try to explain my understanding of the different components you may be challenged with when looking at different libraries:

  • properties, signals or behaviors represent values that may change over time. For example the value of a text box or the position of the mouse pointer on screen.
  • event streams describe phenomena (events) that appear at discrete points in time, like a user click or when a property changes its value. Event streams are not bound to properties, but allow capturing of changes in a discrete manner. Operators like map, filter, scan (also called reduce) allow you to easily create new streams out of other streams. This also follows the functional concept (immutability) in that streams are not altered by the operators, but instead new streams are created. The following figure shows a simple example where the number of clicks are counted and then the count is filtered for even values. Besides these simple operators there are some that allow combining multiple streams into a single new stream, like merge, concat or combine.
Clicks, Counts, Even
  • switching means changing the system in response to events. This is established by using a higher order construct of streams—a metastream, i.e. a stream of streams, where each event is a also stream. At first this sounds confusing but it really builds the foundation for implementing asynchronous flows. Imagine you have a stream where each event is an URL to fetch data from. These URLs are mapped to requests which return a response at any future point in time. The second part really sounds like promises/futures and in fact a promise is just a stream that emits one event (response) and then completes. Thus, now that we have a stream of streams (stream of promises) we can react to the events/responses of each of the streams. Operators like flatMap allow us to flatten the metastream again and thus create a new stream that contains all plain responses, see the following figure:
URLs, Requests, Responses

This was just a quick glance at the concepts of functional reactive programming using properties, event streams and their operators. Event streams provide a great toolset to describe asynchronous data flows, which I will try to underpin in the next paragraph presenting real life use cases. For more detailed examples and thoughts about assembling event streams I suggest reading Introduction to Rx by André Staltz.

Use Cases

Enough theoretical content, let’s see some real examples. Many applications have to deal with real-time events and should handle them in a responsive way, e.g. a web-frontend has to react to user events like key presses or mouse clicks. FRP allows us to describe this behavior in a very concise and readable way. There are many different libraries for various programming language and with a varying set of functions (e.g. Bacon.js, Frappuccino, RAC). Along the next paragraphs I am going to use different libraries to show some fields of application, but in their cores all libraries follow the same principles.

Web-Frontend

Web-Frontends are typically very interactive environments. Most of the time the application has to react to user input immediately to provide a good user experience. Below I implemented a small but comprehensive example of an instant search input field, which would be a pain using traditional methods—you would have to manage timers, save state variables, define a lot of callbacks and manage AJAX calls. My library of choice was Bacon.js, which can be used in frontends as well as in backends. The example is almost self explanatory:

var searchText = $("#search")
  .asEventStream("keyup")
  .map(function (event) { 
      return event.target.value 
   })
  .skipDuplicates()
  .toProperty('')

var refreshButton = $("#refresh")
    .asEventStream("click")
    .startWith("click")

var searchEvents = searchText
  .combine(refreshButton, function (text, click) {
      return text
  })
  .filter(function(text) {
      return text.length > 2
  })
  .debounce(300)

var searchRequests = searchEvents
  .flatMapLatest(function(text) {
      return Bacon.fromPromise($.ajax("/search/" + text))        
  })
  .map(function(json) {
      return json.results
  })

var searchResults = searchRequests.toProperty([])
searchResults.onValue(function (result) {
    //render result array into html
})

First, the keyup events of the search textbox are converted into an eventstream. Then, instead of using every single key press, each one is mapped to the full string currently in the textbox. Afterwards the event stream is converted into a property that represents the text inside the search box and emits changes immediately—duplicate values are ignored due to the skipDuplicates filter. Additionally, a refresh button acts as another search trigger. Both triggers are then combined into a new event stream that is going to fire up a search request later on. Notice how the default value of the searchText property and the startWith event of the refreshButton ensure valid start values of the combine method. This new search event stream is additionally filtered by the length of the search term—ignoring search terms that are too short. Moreover it is debounced by 300 ms in order to not trigger a separate search for every key press or multiple clicks within a small timespan. This shows another very useful building block of FRP namely scheduling events.

After having set up these search events, they are used to trigger the real search request. Each emitted event is mapped to a promise—representing the result of the an async search request. Wrapping the promise itself into an event stream allows us to treat it as ordinary event stream and apply our well known operators. But it has to be flattened afterwards in order to pass the result of the request as an event to the newly created stream. Besides flattening the stream, only the result of the latest request is being propagated into the stream—this is an example of switching, using the flatMapLatest operator. This shows the real power of higher order event streams, it allows changing the behavior of the system depending on the child event streams. Finally, the resulting stream is converted into a property with an empty initial value—thereby it represents the most recent search results starting with an empty array. All that remains to do now is to listen on changes of the search results property and subsequently render the results onto the webpage.

In my opinion it takes some time to wrap your mind around the concept of event streams, but it’s definitely worth it. The code is very concise and readable due to the declarative nature—to top it all off, you don't have to worry about asynchronous scheduling and thread safety.

Mobile-Frontend

Another very interactive environment are mobile applications—user interaction, phone calls, push notifications or sensor inputs like GPS demand a very flexible handling of events. As an iOS developer, you might have experienced clunky controllers that definitely do too much work. Therefore the MVVM pattern received a lot of popularity recently as an alternative to the traditional MVC pattern. Further ReactiveCocoa (RAC) became the tool of choice to implement the binding between view and view model, or improving asynchronous task handling. It is able to replace things like delegates, KVO, block callbacks, target action patterns and notifications. In RAC there is the notion of a signal which is very similar to a property in BaconJS. You are able to subscribe to a signal in order to receive a stream of change events over time and then let you apply operators like map or filter. The following example shows how the attribute username is wrapped into a signal using RACObserve. Then a new signal is branched off to only emit valid usernames and finally, each valid username is printed to the log.

RACSignal *userNameSignal = RACObserve(self, username)

RACSignal *validUserNameSignal = [userNameSignal
  filter:^(NSString *name) {
    return [name length] > 5;
  }];

[validUserNameSignal subscribeNext:^(NSString *name) {
  NSLog(@"Username %@ is valid", name);
}];

Additionally the library provides a lot of convenient functions to convert Obj-C/Cocoa elements into RAC signals and vice versa, like textboxes or buttons. The example below shows two useful helpers:

RAC(self.logInButton, enabled) = [RACSignal
  combineLatest:@[
    self.usernameTextField.rac_textSignal,
    self.passwordTextField.rac_textSignal
  ] reduce:^(NSString *username, NSString *password) {
    return @(username.length > 0 && password.length > 0);
  }];

RAC is a macro which allows setting a value of an object in dependency of a signal. Further, the library enriches existing Cocoa classes with predefined signals, as UITextField provides a rac_textSignal that fires on each text change. The example above just listens to changes in the username or password and immediately enables or disables a login button. There is much more to explore in RAC, like two-way binding using RACChannels, hence I like to refer to the bounty of resources available online (Readme, RayWenderlich, NSHipster).

Backend

Nowadays data is often distributed over large networks. Therefore many backend systems need to gather data from different sources in order to respond to a single request. Due to performance reasons those sources are often queried asynchronously and results are cached locally. FRP provides us with a consistent interface to describe such data flows easily, namely event streams also called observables in the Rx world. Observables already possess an asynchronous nature and therefore abstract away parallel execution details. Further they can be chained, combined or splitted easily to allow flexible composition of multiple tasks. Netflix uses observables to optimize their backend service layer with their own JVM implementation of Microsofts Rx(Reactive Extension) libraries. The following example shows how simple it is to make caching transparent to the caller and how readable the resulting code is:

def Observable getData(int id) {
  if(cache.contains(id)) {
    return Observable.create({ observer ->
      observer.onNext(cache.get(id));
      observer.onCompleted();
    })
  } else {
    return Observable.create({ observer ->
      // do heavy work on separate thread
      executor.submit({
        try {
          T value = getFromRemoteService(id);
          observer.onNext(value);
          observer.onCompleted();
        } catch(Exception e) {
          observer.onError(e);
        }
      })
    });
  }
}

Another example shows how to compose multiple observables in order to collect data from different sources:

def Observable getDashboardGrid(userId) {
  getCategories(userId).mapMany({ Category category ->
    category.getVideos()
      .take(10)
      .mapMany({ Video video -> 
        def m = video.getMetadata().map({ 
          Map md -> 
            return [title: md.get("title"), length: md.get("duration")]
        })

        def b = video.getBookmark(userId).map({ 
          position -> 
            return [bookmark: position]
        })

        def r = video.getRating(userId).map({ 
          VideoRating rating -> 
            return [rating: 
              [actual: rating.getActualStarRating(),
              average: rating.getAverageStarRating(),
              predicted: rating.getPredictedStarRating()]]
        })

        return Observable.zip(m, b, r, {
          metadata, bookmark, rating -> 
            return [id: video.videoId] << metadata << bookmark << rating
        })
      })   
  })
}

The function getDashboardGrid collects all relevant data for a user's dashboard. It starts by getting all categories the user is interested in and then maps each category to a list of 10 popular videos in that category. In addition, for each video multiple sources a queried in order to get their metadata, bookmark information, and different ratings. Every result of these queries is again an observable, which removes the need to differentiate between synchronous and asynchronous calls. Finally, all information are zipped together into one resulting stream per category that contains all the information needed to build a dashboard.

A more detailed description on how Netflix applies these concepts can be found on their blog Reactive Programming in the Netflix API with RxJava. But the most important part to pick up on is that FRP leads to a stable and transparent interface regardless of concurrency or caching. Further, all implementation details can be modified later on without affecting the published interface many other developers may rely on.

Conclusion

Functional reactive programming provides us with a toolset to easily describe complex concurrent data or control flows in a declarative way using a clear interface—it’s all about what and not how! Indeed you have to learn a new paradigm, which adds another level of abstraction, but in the end it feels very natural. One major drawback is the ability to debug reactive code, because events fly around at many points in time. I ignored error handling in all examples, which is in fact not a big deal and built into event streams by design. FRP is not the holy grail and has to be chosen wisely for the right job—like data binding. There are also many alternatives like Meteor Tracker which try to provide similar functionality. I hope you enjoyed this short overview about functional reactive programming. If you want to dig deeper you may find the platform ReactiveX useful.

Partner für digitale Geschäftsmodelles

Ihr sucht den richtigen Partner für eure digitalen Vorhaben?

Lasst uns reden.