Even more on FRP and F#

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:

  • We will see the last example on Computation Expression defining effectively what we will apply in that context
  • We will analyze the F# Obeservable module

Railway-oriented Programming

We 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:

Railway-oriented Programming

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) 

Making an Observable in FRP

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

add

('T -> unit) ->   IObservable<'T> -> unit

choose

('T -> 'U option) ->   IObservable<'T> -> IObservable<'U>

filter

('T -> bool) ->   IObservable<'T> -> IObservable<'T>

map

('T -> 'U) ->   IObservable<'T> -> IObservable<'U>

merge

IObservable<'T> ->   IObservable<'T> -> IObservable<'T>

pairwise

IObservable<'T> ->   IObservable<'T * 'T>

partition

('T -> bool) ->   IObservable<'T> -> IObservable<'T> * IObservable<'T>

scan

('U -> 'T -> 'U) ->   'U -> IObservable<'T> -> IObservable<'T>

split

('T -> Choice<'U1,'U2>)   -> IObservable<'T> -> IObservable<'U1> * IObservable<'U2>

subscribe

('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 element
  • print to process all the elements one by one and display their values
  • ofSeq to cast a sequence in a observable list
..................Content has been hidden....................

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