In the previous chapter, we introduced a 360-degree description of FRP. We discussed the main features related to this type of programming, passing from a theoretical introduction to the concrete examples of the scenarios. We also described how the Event
module of the F# language exposes functionalities to manage the Event
type delegate in the .NET Framework and how this module amplifies its capabilities, adding also a set of functionalities to manage events, such as collection (the IObservable
interface). Finally, we conclude the chapter showing the main differences between push-based and pull-based scenarios.
FRP is not a simple subject to understand; in fact, it is frequently considered very difficult to assimilate. This problem depends on the complexity of the concepts; anyway, once you understand their meaning, it is not so straightforward to put them into practice. In other words, the only relatively simple aspect is the presence of objects that can create a more complete description of FRP.
This chapter aims to examine in depth the previous introduction of FRP, implementing the description with other information in order to have a general and complete overview of FRP.
In particular, we will discuss the following:
Discrete and continuous components are fundamental to better understand FRP. From the first introduction, one of the main axes is exactly the distinction between discrete and continuous. In the previous chapter, we analyzed many aspects related to these two components, but we never mentioned them. If we had so, then the argument could have been too complicated and theoretical.
By definition, discrete and continuous components can be described as the two main characteristics which FRP provides. In other words, they are the basics to apply time flow in our application or to improve our scenario of use.
We previously discussed the execution of the following function:
List.map (fun x -> x ** x)
While doing so, we introduced the concept of time flow to underline that every execution of the mathematical function power occurs step by step.
In the example, it seems that this scenario is continuous, because for every input value of the list, a new output value is generated.
Actually, with a more in-depth analysis, List.map
could be seen as a hybrid scenario. In fact, the exponentiation of every single value shall be made according to the order of the elements, which is a prerogative of discrete scenarios.
As a consequence, we will obtain combinators made up of behavior and Event
.
In the next sections, we will discuss in more detail about discrete components, while continuous components will be treated later.
The Event
module is the basis of discrete components. They are the milestones of FRP in an event-driven context.
In the previous chapter, we introduced the concept of signals.
In other words, each event is able to modify a reactive system independently from other signals. We will consider the following graphic:
Each orange arrow shows the occurrence of an event. We can easily understand that it is impossible to determine when an occurrence happens between a signal and the next one.
In a discrete component, events are considered as a set and they are called Event Stream
. This event flow is nothing but the scenario called push-based in the previous chapter:
open System.Windows.Forms let form = new Form(Text = "F# Windows Form", Visible = true, TopMost = true) form.MouseMove |> Event.filter ( fun evArgs -> evArgs.X > 100 && evArgs.Y > 100 && evArgs.X < 255 && evArgs.Y < 255) |> Event.add ( fun evArgs -> form.BackColor <- System.Drawing.Color.FromArgb( evArgs.X, evArgs.Y, evArgs.X ^^^ evArgs.Y))
It is interesting to notice that we can decide how to act once the occurrence happened. We can't stop the event, but only filter or ignore it.
For example, the graphical representation of the function y=x 2 is discrete if the axis domain is composed only of integers. As a result, we will obtain a set of separated points, but in a countable range.
In the next example, the Charting
library using NuGet is used to represent data. Notice that it is necessary to install the library if you want to run the code and pay attention to the current version of the library.
We will start with the following F# code:
#r @"../packages/FSharp.Charting.0.90.14/lib/net40/FSharp.Charting.dll" #load @"..packagesFSharp.Charting.0.90.14FSharp.Charting.fsx" open System open System.Drawing open System.Windows.Forms open FSharp.Charting let list = [for x in -10 .. 10 -> (x, x * x)] Chart.Point(list,Name="Integer")
We will obtain this graphic:
To sum up, a discrete component has the following features:
It is very important to remember that, when we discuss about events in F#, we are dealing with the type Event
of the module in the assembly Fsharp.Core.dll
with the namespace Microsoft.FSharp.Control
, not with DelegateEvent
of the .NET framework.
In fact, this particular type of events offers many more functionalities, as previously seen.
When we presented an example of code related to the events, we confined ourselves to filter the stream of occurrences by using the position of the mouse. However, thanks to F#, now it is possible to apply more powerful flow controls:
open System open System.Windows.Forms let form = new Form(Text = "F# Windows Form", Visible = true, TopMost = true) form.MouseMove |> Event.choose(fun evArgs -> match evArgs.Button with | MouseButtons.None -> None | _ -> Some( evArgs.X, evArgs.Y)) |> Event.filter (fun (x, y) -> x > 0 && y > 0 && x < 255 && y < 255) |> Event.add (fun (x, y) -> form.BackColor <- System.Drawing.Color.FromArgb( x, y, x ^^^ y))
The Event.choose
function is similar to Event.filter
; however, it creates a selection of messages from the original event, so it creates a new message.
Notice that through Pattern Matching and Built-In Discriminate union (option-choice), we can pass values with different types using Some
and we can also stop the execution using None
when MouseButtons
is in the status none
.
Continuous components differ from discrete ones in many aspects. Anyway, together, they represent one of the main axes of FRP.
In this specific case, signals are called behavior and they change continuously over time. In other words, a continuous component is composed of a constant value (signals) that can become any value during the time flow in a set of definite or indefinite values.
The following graphic shows how the time flow of a behavior works over time:
Unlike a discrete scenario, there are no events in a continuous component, but a continuous change flow. Therefore, if a signal changes continuously, the calculation performed at each modification will be greater, since it will occur many more times.
When working with continuous components, we can find a latency state and the scenario will be different because it will be pull-based. Regardless of how many times signals change, every new state of these signals will be elaborated, almost exclusively, in an asynchronous context.
In a continuous implementation, it is not possible to count the number of occurences (changes), but every change will certainly be elaborated.
We can see the mathematical example and the related F# code again.
If we consider the function y= x2 and we move the axis domain from integer to real, we will obtain a continuous line, not a set of different points. This will happen because the scenario will move from discrete to continuous, since any value will be included in the set of real numbers.
As a consequence, the representative F# code will be as follows:
#r @"../packages/FSharp.Charting.0.90.14/lib/net40/FSharp.Charting.dll" #load @"..packagesFSharp.Charting.0.90.14FSharp.Charting.fsx" open System open System.Drawing open System.Windows.Forms open FSharp.Charting let list = [for x in -10.0 .. 10.0 -> (x, x ** 2.0)] Chart.Line(list,Name="Float")
We will get the following graphical output:
In conclusion, a continuous component has the following features:
In F#, if we want to represent a discrete scenario, we have the module Event
at our disposal, which expresses the Event Stream
concept in detail.
However, in a continuous component, we do not find the type Event
; in fact, it is more correct to talk about values and constants.
A good example of behavior is any object Time
that continuously changes values over time. This modification can be negative or positive, but it certainly has different values in two different moments of the time flow.
It is not easy to find other cases in which it is simple to imagine an object that changes over time. Actually, in the example of the code used in the previous chapter, there is a kind of value that frequently changes:
form.MouseMove |> Event.filter( fun evArgs -> evArgs.X > 0 && evArgs.Y > 0 && evArgs.X < 255 && evArgs.Y < 255)
We have already defined a value that changes over time. The example shows how the position of the mouse (struct point) updates at every movement of the mouse with the value of the position of the absolute coordinates in the application window.
This is another good example of behavior. So, why did we previously consider it as an example of Event Stream
?
In FRP, Event
and behavior, such as discrete and continuous, are conceptually similar and together they represent one of the main fundamentals.
By definition, we can state that:
Event a ≈ Behavior (Maybe a) => Signals
Symbol ≈
means circa or approximately the same.
Now that we understand the main object of FRP, we can go further and analyze the common use scenario that includes both the concepts discussed up to this point.
The use of the signals is almost always essential in cases in which the system has to respond in realtime. Usually, these scenarios have as their objective the need to respond to incentives and they have to occur in a definite period of time. As a consequence, an essential aspect that comes to light is the fact that, in a reactive system, it's very important to consider all the costs related to execution time and used memory. Imagine how much it could cost to not analyze these aspects in depth. It might result in memory leak or infinite cycles.
When referring to hybrid system or simply the Real Time FRP (RT-FRP) in FRP, a reactive system is made up mainly of two parts:
Scenarios where it is possible to apply RT-FRP and create a hybrid system are numerous, for example, animations and Graphical User Interface (GUI) seen in the code in the previous chapter.
Otherwise, it is very interesting, considering the application in robotics where responding to incentives in a reactive way is fundamental and it is important to accurately manage all data and the memory used.
Moreover, it is also important to notice that the evolution of technology in the last few years, such as the Cloud and Internet of Things (IoT), opens the way for new possibilities concerning the application of FRP and also the reactive system, or more aptly called the hybrid system, in particular. In fact, thanks to FRP and F#, it is possible to simplify the development of technologies by using the lambda architecture, composite functions, and actors.
In the following section, we will explain in detail how to interact with and integrate RT-FRP with some examples.
3.12.76.164