Time flow and dynamic change

Time flow and dynamic change are now clear concepts of FRP. In the previous chapter, we discussed over and over again what they are. They represent one of the main axes of FRP together with discrete and continuous semantics.

Concerning reactive systems (hybrid systems), we immediately understood how much important the concepts of execution time and used memory are. These two features may have some problems that could make the system nonreactive. The title of this section suggests a solution for them both. The following are the methods which can be used:

  • A good approach to avoid prolonged execution time or, even worse, the inability to manage the time flow; this results in having control on information flow (data or events)
  • Instead, if we want to control used memory, for example, useless information or instances, it would be interesting to use a dynamically changing system according to the information obtained and architecture that already support these two features

F# and any other language based on functional paradigm exposes and uses functions and architecture that already support these two features This is one of main reasons why languages that allow a functional approach are usually used for reactive scenarios

Time flow in asynchronous data flow

In FRP and also in real time scenarios, it is very likely that data flow and its manipulation happens in an asynchronous way.

Anyway, we know that in an asynchronous context it is not possible to establish with certainty the execution time of any operation. We can analyze the following code taken from MSDN ( https://msdn.microsoft.com/en-us/library/ee370262.aspx ):

let bufferData (number:int) = 
    [| for count in 1 .. 10 -> byte (count % 256) |] 
    |> Array.permute (fun index -> index) 
 
let writeFile fileName bufferData = 
    async { 
      use outputFile = System.IO.File.Create(fileName) 
      do! outputFile.AsyncWrite(bufferData)  
    } 
 
Seq.init 10 (fun num -> bufferData num) 
|> Seq.mapi (fun num value -> writeFile ("file" + num.ToString() + ".dat") value) 
|> Async.Parallel 
|> Async.RunSynchronously 
|> ignore 

The function Array.permute performs the mapping from the input index to the output index.

The keyword do! and the asynchronous counterpart of the keyword do is the equivalent of the instruction let () = expr., that is, the execution of an expression that returns a value unit type.

If we try to perform the function in Interactive Console of F#, in the Windows Temp folder, the following code will generate 10 files with 1000 chars in each one.

Anyway, if instead of 10 files, there are thousands or even worse infinite files, then it would not be possible to determine the time flow. In this case, it would be pointless to add a timeout to control the flow. Take a look at the following code:

let bufferData (number:int) = 
    [| for i in 1 .. 1000 -> byte (i % 256) |] 
    |> Array.permute (fun index -> index) 
 
let counter = ref 0 
 
let writeFileInner (stream:System.IO.Stream) data = 
    let result = stream.AsyncWrite(data) 
    lock counter (fun () -> counter := !counter + 1) 
    result 
 
let writeFile fileName bufferData = 
    async { 
      use outputFile = System.IO.File.Create(fileName) 
      do! writeFileInner outputFile bufferData 
    } 
 
let async1 = Seq.init 1000 (fun num -> bufferData num) 
             |> Seq.mapi (fun num value -> 
                 writeFile ("file_timeout" + num.ToString() + ".dat") value) 
             |> Async.Parallel 
try 
    Async.RunSynchronously(async1, 200) |> ignore 
with 
   | exc -> printfn "%s" exc.Message 
            printfn "%d write operations completed successfully." !counter 

Through the second parameter of the method Async.RunSynchronously(async1, 200), we can set up the maximum execution time value in order to be able to control the time flow.

In this specific case, it will stop the flow that generates files when the countdown hits zero. In fact, if we look into the Temp folder or simply read the output message in Interactive Console, we can find a number of generated N files that can change slightly at every execution, according to CPU work.

Note

You should take into account that the number of generated files depends very much on your personal computer configuration and performance.

Using F# and collection function for dynamic changing

When we presented F# and FRP in particular, we introduced the following set of essential concepts:

  • Everything can be considered as a stream of data or events
  • A reactive system is dynamic and flexible, and allows the management of a time flow

Now, if we try to put together these features, we can obtain an evolution of FRP, or better, a chance to organize our code and logical architecture through a flow of choices.

To be able to handle a set of choices, such as a set of objects or functions, first of all we should have a common denominator which represents them, otherwise it won't be possible. In informatics term, we could say switch from a concrete implementation to an abstract one.

In particular, functional programming has a strict connection with mathematics, so we should create Computation Expressions. These expressions are inspired by the Monads of the functional language Haskell, which in turn are inspired by the Monads concept in mathematics.

Computation Expressions are merely expressions that execute a function given an input and return a result (output). Otherwise, in terms more similar to programming, they are interfaces with rules for the execution of their own methods. For example, this allows us to connect more Monads and handle a workflow.

In F#, one of the simplest representations of Computation Expression during the flow of choices is the following:

type Result<'TSuccess,'TFailure> =  
    | Success of 'TSuccess 
    | Failure of 'TFailure 

This code represents a general discriminate union that has as a possible result Success or Failure of the type 'TSuccess or 'Tfailure, respectively.

The next code shows how we can take full advantage of Monads, connecting more functions that return always the same output:

type Result<'TSuccess,'TFailure> =  
    | Success of 'TSuccess 
    | Failure of 'TFailure 
 
let bind inputFunc =  
    function 
    | Success s -> inputFunc s 
    | Failure f -> Failure f 
 
 
type Account = { UserName : string; IsLogged : bool; Email : string  } 
 
let validateAccount account = 
    match account with 
    | account when account.UserName = "" -> Failure "UserName is not valid" 
    | account when account.Email = "" -> Failure " Email is not empty" 
    | _ -> Success account 
 
 
let checkLogin account = 
    if(account.IsLogged) then 
        Success account 
    else 
        Failure "User is not logged" 
 
let LogIn account = 
    if(account.IsLogged) then 
        Failure "User has already Logged" 
    else 
        Success {account with IsLogged = true} 
 
let LogOut account = 
    if(account.IsLogged) then 
        Success {account with IsLogged = false} 
    else 
        Failure "User has already Logged" 
 
let ProcessNewAccount =  
    let checkLogin = bind checkLogin 
    let login = bind LogIn 
    validateAccount >> login >> 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" 

As you can see, we created different Monads: validateAccount, checkLogin, and LogIn, which return a type Result<Account,string> as a result. Later, we created a function called ProcessNewAccount that, through the use of the composition operator, (>>) connects each Monads in a definite flow. It is important to note how, for every function, it is necessary to use the method bind to avoid cast and anonymous type errors.

In the last row, three different accounts are defined, which are processed with the function ProcessNewAccount.

The result obtained through Interactive Console is as follows:

Result = Failure "UserName is not valid" 
Result = Failure "User has already Logged" 
Result = Success {UserName = "User1"; 
         IsLogged = true; 
         Email = " [email protected] ";} 
..................Content has been hidden....................

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