The second half of the chapter shows how we can make the best use of F# and what the Microsoft .NET Framework makes available to enjoy and adopt FRP.
In the last few years, technology has evolved at an increasing rate. Nowadays, everything is connected and, in the future, each and every device will be able to receive and send data to and from each other. This will certainly lead to a technical evolution about the way we write the code. Let's think, only for a minute, about the notification messages that appear in our smartphone, our personal computer, and even in our smartwatch. All these need is a reactive system that is able to send millions if not billions of these messages simultaneously on multiple devices.
This is why functional programming and (Functional) Reactive Programming in particular play a key role in this evolution.
Social networks, e-commerce servers dedicated in sending information from and to IoT devices, are already using hybrid systems that complement part of their architecture as a reactive system.
They communicate through signals that are constantly processed in a time flow, sometimes well defined and sometimes indefinite. In the past, FRP was applied only to a specific domain, such as image elaboration or military terms. Now, even front end-web framework in JavaScript is designed for a reactive use (see Bacon.js https://baconjs.github.io/ ).
In this introduction, we summarized most of the concepts detailed in the previous chapters. Now, we will examine in depth some of the possibilities offered by the F# language and .NET Framework, particularly the following:
Obeservable
moduleWe know that F# and functional paradigms can be recognized, in the previous example regarding Monads, as a particular programming technique. We are talking indeed about Railway-oriented Programming, so let's figure out what it is. Railway-oriented Programming refers to a programming technique that connects different functions in a composition function (called path
), which has the task of sending the output of a single request to the following function in case of the success or failure of the management of the exception, prematurely ending the execution of the composition function itself.
The following image represents an execution path using this technique and is based on the function ProcessNewAccount
of the previous example:
As you can see, the diagram depicts three functions held in the Computation Expression that return Success or Failure. In case of failure, we can notice how the flow stops without passing to the next Monad and, in this specific case, it returns a string:
let ProcessNewAccount = let checkLogin = bind checkLogin let login = bind LogIn validateAccount >> login >> checkLogin //OutPut in interactive //val ProcessNewAccount : (Account -> Result<Account,string>)
It becomes clear that one of the aims of Railway-oriented Programming is the unified management of errors in a process. However, this kind of programming can be used in other domains provided that they can all be translated in a choice.
F# helps us in implementing this technique, presenting a general type that has whatever is necessary to manage a Monad without having to create a custom interface.
In the assembly FSharp.Core
, there are a lot of types called Choice
that contain a set of overloads for the type Choice
varying from 2
to 8
in the multiple implementations. Actually, we are interested in the following one that has two choices:
[<StructuralEquality>] [<StructuralComparison>] type Choice<'T1,'T2> = | Choice1Of2 of 'T1 | Choice2Of2 of 'T2 with interface IStructuralEquatable interface IComparable interface IComparable interface IStructuralComparable end
If we change the previous code with the implementation of the type Choice
, the code in the example would be easier and concise, eliminating the general type
Result<'TSuccess,'TFailure>
, as you can see in the following code:
let bind inputFunc = function | Choice1Of2 s -> inputFunc s | Choice2Of2 f -> Choice2Of2 f type Account = { UserName : string; IsLogged : bool; Email : string } let validateAccount account = match account with | account when account.UserName = "" -> Choice2Of2 "UserName is not valid" | account when account.Email = "" -> Choice2Of2 " Email is not empty" | _ -> Choice1Of2 account let checkLogin account = if(account.IsLogged) then Choice1Of2 account else Choice2Of2 "User is not logged" let LogIn account = if(account.IsLogged) then Choice2Of2 "User has already Logged" else Choice1Of2 {account with IsLogged = true} let LogOut account = if(account.IsLogged) then Choice1Of2 {account with IsLogged = false} else Choice2Of2 "User has already Logged" let ProcessNewAccount = validateAccount >> (bind LogIn) >> (bind checkLogin) let NewFakeAccount = { UserName = ""; Email = ""; IsLogged = false } let AccountLogged = { UserName = "User"; Email = "[email protected]"; IsLogged = true } let NewAccount = { UserName = "User1"; Email = " [email protected] "; IsLogged = false } ProcessNewAccount NewFakeAccount |> printfn "Result = %A" ProcessNewAccount AccountLogged |> printfn "Result = %A" ProcessNewAccount NewAccount |> printfn "Result = %A"
It is important to notice how much Railway-oriented Programming is strictly connected to FRP. In fact, thanks to these composed functions, it is possible to manage the personalized Monads flow in a reactive way. For example, we can create one for LogOut
:
let ProcessLogOutAccount = validateAccount >> (bind LogOut) >> (bind checkLogin)
We can also create another one for the validation and the login state:
let ControlAccount = validateAccount >> (bind checkLogin)
Finally, we could even connect the composed functions to each other:
let ControlAndLoginAccount = ProcessNewAccount >> (bind ControlAccount)
In the previous chapter, we presented how the type FsharpEvent
from the assembly Fsharp.Core
includes a set of methods for event management:
type Event<'T> = class new Event : unit -> Event<'T> member this.Trigger : 'T -> unit member this.Publish : IEvent<'T> end
The interface IEvent
inherits in turn IObservable
and IdelegateEvent
from two other interfaces.
The first one defines an agreement for the shipping of push-based notifications. In other words, we are talking about the same type used in .NET. In fact, its counterpart is the interface IObserver
, which represents the agreement of notification reception.
Such as for the module Event
, also for the type IObservable
there exists a module called Observable
that exposes a set of functions useful for the management and registration of observer.
Also, in this case, by using the guideline MSDN at the link https://msdn.microsoft.com/en-us/library/ee370313(v=VS.100).aspx, we can find a table that summarizes the functions of the module.
In this short extract, we report single methods with their signatures:
Function |
Value
|
|
('T -> unit) -> IObservable<'T> -> unit
|
|
('T -> 'U option) -> IObservable<'T> -> IObservable<'U>
|
|
('T -> bool) -> IObservable<'T> -> IObservable<'T>
|
|
('T -> 'U) -> IObservable<'T> -> IObservable<'U>
|
|
IObservable<'T> -> IObservable<'T> -> IObservable<'T>
|
|
IObservable<'T> -> IObservable<'T * 'T>
|
|
('T -> bool) -> IObservable<'T> -> IObservable<'T> * IObservable<'T>
|
|
('U -> 'T -> 'U) -> 'U -> IObservable<'T> -> IObservable<'T>
|
|
('T -> Choice<'U1,'U2>) -> IObservable<'T> -> IObservable<'U1> * IObservable<'U2>
|
|
('T -> unit) -> IObservable<'T> -> IDisposable
|
Through these functions, it is possible to create a subscriber, such as in C#. Then, we can use it to manage notifications:
module Observable = open System let ofSeq (values:'T seq) : IObservable<'T> = { new IObservable<'T> with member __.Subscribe(obs) = for x in values do obs.OnNext(x) { new IDisposable with member __.Dispose() = () } } let inline print (observable:IObservable< ^T >) : IObservable< ^T > = { new IObservable<'T> with member this.Subscribe(observer:IObserver<'T>) = let value = ref (Unchecked.defaultof<'T>) let iterator = { new IObserver<'T> with member __.OnNext(x) = printfn "%A" x member __.OnCompleted() = observer.OnNext(!value) member __.OnError(_) = failwith "Error" } observable.Subscribe(iterator) } let first (obs:IObservable<'T>) : 'T = let value = ref (Unchecked.defaultof<'T>) let _ = obs.Subscribe(fun x -> value := x) !value let obsValue = {0.0..100.0} |> Observable.ofSeq |> Observable.filter (fun x -> x % 2.0 = 0.0) |> Observable.map (fun x -> x ** 3.0) |> Observable.print |> Observable.first
In the preceding example, the module F# has been extended with the following functions:
first
to return the first subscribed elementprint
to process all the elements one by one and display their valuesofSeq
to cast a sequence in a observable list3.136.233.153