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:
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.
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.
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.
This introduces concept of time flows, which will be described in detail in the following chapters.
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.
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
.
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:
Observable
pattern
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.
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.
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:
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:
ValueChanged
event related to any control that supports it can modify one or more of the three RGB channels.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 |
|
Runs the given function each time the given event is triggered. |
|
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. |
|
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. |
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:
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.
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:
IObervable<T'>
ButtonClick
For pull-based scenarios:
AsyncSeq<T'>
.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.
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.
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.
18.225.55.193