Functional Reactive Programming

FRP, the central theme of the second half of the chapter, may be considered by definition as a programming paradigm. In fact, this paradigm is different from the more common ones, such as imperative, object-oriented and functional, because it applies only to Reactive Programming. The context of use is much smaller.

In the previous chapters, we understood how the library Rx represents all these data sequences as observable sequences and how it can be used to compose asynchronous and event-based programs.

Similarly, we will now introduce FRP exploiting objects and, in general, all the features of F# and functional programming. This is possible due to the inherent ability of the language to make almost every instruction in a function block.

In the following sections, we will see in detail the following points:

  • What is FRP and how can you represent it?
  • What are the main features and applicable scenarios?
  • The concept of asynchronous data flow
  • The types on push-based and pull-based

What is FRP and how is it represented?

We previously introduced FRP (acronym for Functional Reactive Programming) as a type of paradigm. Actually, it is not a real paradigm because it can be described using simple functions, similar to many other functional patterns.

To better understand this concept, we should take a little step back and discuss object-oriented programming, especially events. Events are everywhere and are the basis to understand FRP. Managing them individually is a very simple procedure, but if we want to develop more advanced functionalities, such as event handling, then it will become more complex.

Observer is a kind of design pattern based on events and it is one of the most common patterns in object-oriented programming. Furthermore, it is the basis of Reactive Extensions and event flow, as mentioned in the previous chapters.

Note

Instead, in functional programming with F#, Reactive Programming is supported natively through the functions themselves.

Reactive programming together with the functional paradigm explains the concept of FRP. This simplification is a starting point to better understand basic concepts up to more complicated ones.

Since FRP is based on functions, will it be possible to understand or simply represent it in .NET Framework and F#? Actually, we have already seen numerous examples.

Note

Consider the following code:

let numberList = [ 2.0; 4.0; 6.0]
let result = numberList |> List.map (fun x -> x**2.0)

The symbol ** is the mathematical operator that represents the mathematical function power. It is recommended in particular that the elements are of the double type.

The map method is a suitable example of FRP. In the same way, almost all the members of the type method of collections are appropriate examples, such as filter, reduce, zip, and many others.

In functional programming, one of the main features is immutability. Consequently, the methods that operate on the elements of collections don't modify the list itself, but instead they process a function event on every single element creating a new object.

In particular, in FRP, there are two different fundamental types of data: Event and behavior.

A value of the behavior type is characterized by the ability to continuously change over time. A classic example of behavior is all the time objects, but it could also be an int type or a constant (object color).

A value of the event type is instead a sequence of the occurrence of the Event itself with a value for each occurrence of the value type.

In the preceding example, both the types are represented: a flow of events that change the values in every element of the list by adopting the function power and returning a new sequence with different values.

Note

In other words, it is possible to combine different behaviors and events (combinators) together to create new ones, as seen in the example.

This introduces concept of time flows, which will be described in detail in the following chapters.

Introduction to functional reactive programming

FRP is defined as a programming paradigm for Reactive Programming. It is actually much more than that.

When we program in .NET, events are everywhere. Many of these events are based on the delegate called EventHandler and the base class called EventArgs. Along with object-oriented programming, they are used for the technique called event programming.

Note

In Visual Studio, if you create a WinForm project, it will be clearly seen as it is set to work on events. Not only this, even the newest template Windows Presentation Framework (WPF) fully supports the programming events. Though, with the latter, it is more appropriate to use the Model View ViewModel (MVVM) pattern for a series of structural advantages and more.

With the expansion of asynchronous programming, the event-based model has evolved as well. As a consequence, in the .NET Framework, a standard called Event-based Asynchronous Programming (EAP) was developed. It will only make available the asynchronous methods that run on a different thread of the same operation as the corresponding synchronous method. Also, some of these classes exhibit events for flow control as asynchronous, such as methodCompleted or methodCancel.

Note

FRP extends a host programming language with the notion of time flow. In this way, it will be possible to obtain powerful control over the flow of data through a model of observation (the Observable pattern).

Collections and functions in a flow

In the previous chapters dedicated to LINQ and C#, it has been shown that collections are at the heart of reactive programming. We will explain an example of FRP in F# that includes all the main features:

  • The concept of time flow
  • The intrinsic characteristics of the event flow
  • The concept of asynchronous data flow and the Observable pattern

Collections and functions in a flow

In the preceding graphic, the line with circular points shows the three input values of the list, while the line with square points, shows three output values to which the function List.map is adopted. It is important to notice how the function is gradually applied to every single value, respecting exactly the input time flow.

It means that the event of execution of the function is processed for each value added in the list. Furthermore, thanks to functional programming (in this case, to F# and the .NET Framework that provides this method by default) and its intrinsic features, this execution is completely thread safe. So, as a consequence, in terms of reactive programming, it is possible to obtain an asynchronous data flow.

FRP and its scenarios

We briefly introduced FRP and how we can use it in software applications. Actually, FRP is applicable not only to basic implementations as a synchronous or asynchronous event flow handling. We also can introduce a set of features able to solve different problems in a very simple way.

Note

Thanks to FRP, we can have a full-scale supervision of the entire event flow introducing the concept of continuous time, where events and behaviors are stream transformers.

So, we can see which possible scenarios are appropriate for FRP.

As previously described, given an event flow, we can execute some functions maintaining time flow control in a particular domain.

Therefore, there are particular domains suitablefor the reactive system. They are as follows:

  • Interactive computer animation
  • Robotics
  • Control and real-time systems
  • GUIs

Event data flow

We assumed so far, especially in the examples in the previous chapters, that in FRP, there is a flow of events and that these events can be defined as signals. Moreover, we understood that the workflow is well defined and it is processed in a synchronous way. Nevertheless, signals change continuously, so it could be necessary to interrupt or modify the flow. It could be essential having an asynchronous data flow.

Graphic applications better related to GUI are definitely some of the most interesting cases where we can verify how synchronous event data flows work.

For example, we can imagine the change of position or color of a graphic object inside an application.

It is essential to understand the following three concepts:

  • Changes always occur through an event. For example, in the color type, a ValueChanged event related to any control that supports it can modify one or more of the three RGB channels.
  • Changes can occur regularly, but in a completely discontinuous way. Once again, in the case of a change of the color type, it is not possible to predict neither how many times nor when an event originating from the user will occur.
  • It could be necessary to filter or add some events. As an example, we could think of a user that frantically keeps changing the contrast value of an image. It is very expensive in terms of performance, and useless to reprocess the image if the new image is already elaborated.

Fortunately, F# and the assembly Fsharp.Core.dll in particular, can help us, and it makes available the Event type to manage the functions of the events themselves:

type Event<'T> = 
 class 
  new Event : unit -> Event<'T> 
  member this.Trigger : 'T -> unit 
  member this.Publish :  IEvent<'T> 
 end 

<'T> is the syntax that identifies generic type. Moreover, the Event type is also called FSharpEvent. It is important to know to use the object through reflection.

IEvent<'T> represents the interface every single event should have. It does nothing but combine the other two interfaces, IObservable<T> and IDelegateEvent.

In this way, an F# event through IDelegateEvent has at its disposal all the features of delegates of the event type of the .NET Framework. Thanks to the IObservable interface, it also acquires the ability to apply functions to events, such as filtering or mapping, as well as using the lambda expression. This is possible thanks to the Event module of F#, which exhibits all these member functions.

A very interesting example of how powerful it is to use the IObservable interface is the following example. It demonstrates how powerful the IObservable interface is. The code is taken from the Microsoft MSDN website ( https://msdn.microsoft.com/en-us/library/dd233189.aspx ) and it is slightly modified to avoid a runtime error:

open System.Windows.Forms 
let form = new Form(Text = "F# Windows Form", 
                    Visible = true, 
                    TopMost = true) 
form.MouseMove 
    |> Event.filter ( fun evArgs ->  
        evArgs.X > 0 && evArgs.Y > 0 &&  
        evArgs.X < 255 && evArgs.Y < 255)  
    |> Event.add ( fun evArgs -> 
        form.BackColor <- System.Drawing.Color.FromArgb( 
            evArgs.X, evArgs.Y, evArgs.X ^^^ evArgs.Y)) 

Interestingly, here we can see how, in three simple blocks of instructions, we created a form with the relative namespace. The form is composed of two different events, each of them with a specific behavior.

In the preceding code, we can see how the Event.filter function acts as a predicate. In fact, it also requires a lambda expression with the argument of the event (in this specific case, MouseEventsArgs) as a parameter and the results of a condition as a result value.

Consequently, if the condition is respected, the callback Event.add function will be processed and run the body function. For this reason, the background color of the window changes to the movement of the mouse through the coordinate positions of the mouse itself.

Given that the Event module is one of the F# first-class, it exposes a wide set of functions, as in most cases. You can refer to the following link on the Microsoft MSDN website https://msdn.microsoft.com/en-us/library/ee340422.aspx .

Some of the main functions are provided in the following table:

Value

Description

add:('T -> unit) -> Event<'Del,'T> -> unit

Runs the given function each time the given event is triggered.

filter:('T -> bool) -> IEvent<'Del,'T> -> IEvent<'T>

Returns a new event that listens to the original event and triggers the resulting event only when the argument to the event passes the given function.

choose:('T -> 'U option) -> IEvent<'Del,'T> -> IEvent<'U>

Returns a new event that fires on a selection of messages from the original event. The selection function takes an original message to an optional new message.

Push and pull-based domains

In the previous chapter, we analyzed how it is possible to implement and use a synchronous data flow using F#. However, it represents only a part of FRP. In fact, we ignored the asynchronous world.

When discussing FRP, it is important to distinguish the two scenarios because they have two different conceptual domains. The synchronous and asynchronous domains reflect their different characteristics also in the strategy of implementation that is respectively push-based and pull-based.

So, we can better identify the two scenarios:

  • When we refer to synchrony, we are in a push-based oriented context, which is a data-driven context typically used in reactive systems
  • When we refer to asynchrony, we are in a pull-based oriented context, which is a demand-driven context, for example, any kind of behavior

If we think about the example mentioned in the previous section, it is easy to understand that we discussed a synchronous push-based oriented context, so we are in a reactive system.

Note

Usually, when the user or even an automation system generates events in graphical interface of software, we are using a reactive system.

In our specific case, in the .NET Framework, Windows Presentation Foundation (WPF) could be projects that implement events. Moreover, WPF can also implement ObservableCollection.

Let's see the main differences between these scenarios.

For push-based scenarios:

  • Synchronous context
  • Data-driven implementation
  • No control creation
  • An example of the representation interface in F# is IObervable<T'>
  • Events change continuously and randomly, for example, ButtonClick

For pull-based scenarios:

  • Asynchronous context.
  • Demand-driven implementation.
  • How rapidly the events are generated doesn't count; in fact, they are all elaborated because it is an asynchronous context.
  • An example of the representation interface in F# is AsyncSeq<T'>.
  • In this case, it doesn't matter when events change, but rather how they occur. In fact, the term behavior. As an example, usually, the parameter passed by reference is a Time object type.

It is useful knowing that there are particular scenarios in which it is possible to have both a push-based context and a pull-based context.

In the next section, we will find an example of a pull-based scenario.

Examples of scenarios with AsyncSeq

When we introduced the main features that distinguish the two different scenarios and pull-based in particular, we used the AsyncSeq<T'> representation as an example.

Note

AsyncSeq is a sequence in which individual elements are retrieved using an Async computation. It is similar to seq<'a>, where subsequent elements are demand-driven.

The assembly FSharp.Control.AsyncSeq.dll is not a part of the F# libraries natively, so this type of assembly must be downloaded using the NuGet console and referenced into the project:

#r "../packages/FSharp.Control.AsyncSeq.2.0.8/lib/net45/FSharp.Control.AsyncSeq.dll" 
open FSharp.Control 
 
let asyncSeq = asyncSeq {  
   do! Async.Sleep(100) 
   yield 1 
   do! Async.Sleep(100) 
   yield 2 } 
 
let two = asyncSeq |> AsyncSeq.filter (fun x -> x = 2)  
let res = two |> Async.RunSynchronously //result is 2 

To download FSharp.Control.AsyncSeq from NuGet, we just have to select the right project in Package Manager Console and select Install-Package FSharp.Control.AsyncSeq.

The key word do! indicates the asynchronous execution of an instruction without any assignment (corresponding to let! () = istr).

Similarly, concerning the Observable interface mentioned in the preceding lines, another possibility is to filter the elements of the list using the specific method AsyncSeq.filter. The module AsyncSeq, indeed, exposes all the required functionalities of interrogation and modification together with the possibility of using the lambda expression.

The following lines show us how to create a sequence in a completely asynchronous context using the keywords asyncSeq and yield.

Tip

On the other hand, if we tried to create a similar sequence with no asynchrony, then the access to static Sleep method of the Thread class would stop the execution of the thread itself:

let syncSeq = seq {  System.Threading.Thread.Sleep(100)  yield 1 System.Threading.Thread.Sleep(100)  yield 2
}

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

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