Chapter 8. F# and Functional Reactive Programming

F# (pronounced as F sharp) is an open source programming multiparadigm language introduced for the first time by Microsoft in Visual Studio 2010. F# is a first class member of the .NET Framework languages and derives from the ML family of functional languages. F# supports the functional paradigm in addition to the traditional object-oriented and imperative paradigm of the Microsoft .NET Framework. Microsoft Visual F# is the real language implementation of Visual Studio. In the previous chapters, we have already seen some examples of functional programming (the Functional programming section of Chapter 1, First Steps Toward Reactive Programming ) written in C# using Reactive Programming concepts.

In the next two chapters, we will extend the information combining F# and Rx to introduce and understand Functional Reactive Programming (FRP).

In particular, in this chapter, we will see the following topics:

  • The F# language and its syntax as an introduction to Functional Programming
  • The key points to differentiate object-oriented and functional paradigm
  • Functional design pattern (Active Pattern, Pattern Matching, choice result, and so on) and the types to implement them
  • Asynchronous programming in F#
  • Introduction to F# for Reactive Programming
  • A few examples based on collections and the F# Rx functions
  • The concepts of asynchronous data flow and push-pull bases through a real scenario

F# - first time

When we use .NET Framework, the first thought goes to languages such as C# and VB.NET. If you want to write a program or services using a functional paradigm, probably your choice would include other languages, for example, Erlang, Haskell, Scala, Wolframe Language (Mathematica), and so on.

This occurs for a variety of reasons, such as the following:

  • The .NET Framework and its principal languages are related to object-oriented programming
  • The syntax of F# and the applications in the functional paradigm are totally different from any other .NET language
  • F# was born in Microsoft research and later used in specific sectors and environments
  • More functional languages, some of which were created many years before, were thought of exclusively for this paradigm

Contrary to the reasons just written, F# is a very interesting language, because it combines the potential of the .NET Framework with a simple syntax and the functional paradigm.

Introduction to F# and FRP

F# is different from the C# and VB.NET languages. This is mainly due to the basic paradigm.

The principal features of F# are as follows:

  • The types are immutable
  • The concept of null values does not exist
  • The concurrence never occurs if the context is purely functional
  • The intrinsically multiprocessor approach for the reasons listed above

Moreover, writing functions in F# is very simple and advantageous because the syntax is very concise:

let sum a b = a + b 
let add5 = sum 5 
let result = add5 2 // the result value is 7 
let result2 = add5 3 // the result value is 8 

As you can see in the first row of the previous example, a sum function is declared between two values (a, b). In the second row, the function add5 is equivalent to sum 5, where 5 is the value of the parameter a. The add5 variable is a partial function because we have to declare the second parameter if we want to execute sum, as we can see in result and result2:

[0..100] |> List.map (fun x -> x * x)  
   |> List.iter (fun y -> printfn "the value is: %i " y)  

Instead, in the preceding example, you have more instructions concatenated with the |> (pipe forward) operator. In particular, declare a list of int. After this, for each value, it is applied to the square and it returns a new list. In the end, through List.iter, print a visual result value.

By using C# and object-oriented programming (OOP), the code to write these functions will be bigger than in F#. Instead, by using the functional paradigm with C#, the result would be more or less the same.

The functions iter and map are also a useful example to introduce the FRP.

The immutable and deduce type

One of the most important features of F# is the immutability. This is fundamental to writing a function that respects the algebraic principles:

//C# code 
int x = 5; 
x = x + 1; 

In mathematics, no value attributed to x could solve the function x = x + 1. In F#, writing this code directly, without using the keyword mutable, would generate the following error:

//F# code: 
let x = 5 
x <- x + 1 
 
error FS0027: This value is not mutable 

Tip

The operator <- indicates the allocation of the value to the right on the left-hand side (called variable). It is conceptually equal to the = operator in C# or VB.NET.

To execute this code correctly, it will be sufficient to change the declaration of x in the following way:

let mutable x = 6 

The result of the operation using F# Interactive is as follows:

The immutable and deduce type

Note

F# Interactive is a tool available in Microsoft Visual Studio used to run F# directly. It also exists in another version called Fsi.exe and you can find it in the path c:Program Files (x86)Microsoft SDKsF# <version>Framework <version>.

They both allow you to run F # code and the file .fsx (F# script).

Note

Performing the code is simple: you just have to select rows of interest and press Alt + Enter or right-click and use the command Execute In Interactive. The selected text will be executed by generating a result or an exception.

Instead, if you want to run script files, it's preferable to use Fsi.exe from the windows console by specifying the file path; in this way, you will get the result in real time in the output window.

Type inference

Another important feature of the language is its ability to interpret the types. In reality, this is not a real characteristic, but a technique for the system to deduce the type.

Indeed, F# has a powerful type inference system. Take a look at the following code:

//declare a tuple 
let tupleValues = 1, "one" 
 
//unpack values 
let v1, v2 = tupleValues 
 
let sumfloat values = List.reduce (+) values 
printfn "The sum is: %A" (sumfloat [4.6; 10.3]) 

Tip

List.reduce is a function applicable to a list of values and reduces two elements of a collection to a single one. In the preceding example, the function List.reduce (+) values adds the first two elements of the collection. Then, the result is processed with the third element and so on, until the final result is obtained.

The same operation could be written using the operator pipe forward: values |> List.reduce (fun x y -> x + y) or values |> List.reduce (+)

In this example, in the first four lines, we have declared a tuple from which individual values are extracted. The result in the console F # Interactive is the following:

val tupleValues : int * string = (1, "one") 
val v2 : string = "one" 
val v1 : int = 1 

Due to the inherent ability to infer the type, the compiler links v1 and v2 with the correct type, respectively, int and string. Similarly, for the subsequent lines, the first use of the function sumfloat is deduced by the declaration of the float type items of the list [4.6; 10.3]. The result is as follows:

The sum is: 14.9 
val sumfloat : values:float list -> float 

Functions as first class values

The title of this section is not easy to understand. It is true that the functions are the basis of F# and its paradigm, but first class values does not mean anything.

However, to make sense of these words, you need to think in functional programming sense.

The key to understand this is much easier than you might think.

F# is a functional language and, as such, it is functional-oriented.

This is one main difference compared to the other .NET languages, which are object-oriented.

Moreover, if one considers the functions in F#, they are everywhere. All of the examples, even the rows, done so far are functions!

Even the simplest of instructions, let x = 5, is a function.

For correctness, it is good to talk about identities and not pure values. In fact, when you declare an identifier assigning a value, it can no longer be changed.

Being functional-oriented forces F# to comply with certain rules and to have some benefits and drawbacks.

The rules are as follows:

  • A function cannot change the status of the program
  • The result of the function cannot be influenced from the outside
  • Regarding immutability, each identifier is considered as a value and then it can be passed as a parameter or simply be the result of a function itself

The benefits are as follows:

  • The factoring and refactoring is a plus with F# and its functions. Refactoring in F# does not require significant time and does not impact the entire project as in C#. This is true only if we apply functional paradigm.
  • The pattern of composition is the basis of functional programming. In this way, you can interpret and solve any problem, from the most complex one to the simplest. Any function can be divided into simpler functions.
  • The functional programming and, in particular, the syntax of functions make the code more concise and thus easier to maintain.

The drawbacks are as follows:

  • F# is a functional programming language; consequently, it is mathematics-oriented, and writing functional code could take much more time than writing object-oriented code.
  • It is not convenient to develop a whole application using F#, because both the development tools and some aspects of the project, such as the GUI, are not optimized for this language.

Naturally, these features are valid only in a functional context. By declaring an identifier using the keyword mutable, as seen previously, you can change the value of the identifier itself.

Note

In mathematics, a function can be represented in this way: y = f(x). Also, given a function, the same input value will give the same output value. This rule is one of the reasons why, in F#, it's easier to code and avoid concurrency.

Using the Type function for object-oriented programming

F# is not only a functional programming language. This is a fundamental principle to remember. By definition, F# is a programming language that provides support for functional programming in addition to traditional object-oriented and imperative (procedural) programming.

If you want object-oriented programming in F#, the language makes all the necessary constructs available. One of the main types of object-oriented programming is classes.

The syntax to declare a class is as follows:

type CustomerName(firstName : string, middleName, lastName) =  
    member __.FirstName = firstName 
    member __.MiddleName = middleName 
    member __.LastName = lastName 

Tip

The keyword __ is the equivalent of the keyword this in C#.

In the first row of the previous example, the keyword type suggests that CustomerName is a class. Unlike C#, you can see that the constructor signature is declared in the same row.

Instead, the keyword member indicates all the class members. In this particular case, members are properties, but they could be also methods.

Note

It is very important to point out that let declarations in the classes have the same meaning as a private member in C#.

In the following example, you can observe how the signature of the member method is declared and one of these this.SetMutable method allows us to change the mutable value of the private member:

type MyClass(intParam : int, strParam : string) =  
    let mutable mutableValue = 42 
    member this.SetMutable x = mutableValue <- x 
    member this.CurriedAdd x y = x + y 
    member this.TupleAdd(x,y) = x + y 

Like in C# and VB, in these functional languages, derivate and abstract class concepts exist:

type DerivedClass(param1, param2) = 
   inherit BaseClass(param1) 
 
[<AbstractClass>] 
type GeometricBaseClass() = 
   abstract member Add: int -> int -> int  // abstract method 
   abstract member Pi : float // abstract immutable property 
   abstract member Area : float with get,set // abstract read/write property 

Tip

It is interesting to see how the signature of the add method is written. Regarding int -> int -> int, the first two stand for parameters, and the last one is the result.

The code to declare interface is similar to the abstract class signature, as you can see in the following example:

type IGeometricBase = 
   abstract member Add: int -> int -> int  // abstract method 
   abstract member Pi : float // abstract immutable property 
   abstract member Area : float with get,set // abstract read/write property 

The main syntax difference between the abstract class and interface is the signature. In the first one, the parenthesis must be written straight after the name, contrary to the other one.

Collection – The heart of F#

In the .NET Framework, when it comes to collections, the first thing that comes to mind is the Language Integrated Query (LINQ). It is a revolutionary component, included for the first time in .NET version 3.5, which provides a set of instructions and keywords to query objects, in particular, collections.

Note

The peculiarity of LINQ is its origin with functional nature. LINQ implements the functional paradigm through the use of query expressions, through extension methods, and the lambda expression related to the collections of the namespace System.Collection.Generic.

It is easy to see at this point how important the collections and all that concerns them are.

In F#, there are three major types of collection: array, list, and n. Each of them exhibits a set of functions and properties to elaborate the collection themselves.

Array, by definition, is fixed-size, zero-based, mutable collection of consecutive data elements that are all of the same type:

let myArr1 = [| 'a'; 'b'; 'c' |] 

The myArr1 variable identifies an array of chars. The keywords [| |] are used to declare array and, in this case, the values are entered by separating them with a semicolon(;).

There are many other ways to create array, and array of different dimension exist. Some of them are shown in the following examples:

let myArr2 = [| 1 .. 5 |] 
let myArr3 = [| for i in 1 .. 5 -> i * i |] 
let arrayOfZeroes : int array = Array.zeroCreate 5 
let multiArr = [| [|0,0|]; [|1,1|] |] 

Tip

The keyword .. specifies an interval of values and is an abbreviation of for .. in. Moreover, there is a variant that allows us to insert an increment value, as follows:

let arr = [|0..4..16|]  
//val arr : int [] = [|0; 2; 4; 6; 8; 10; 12; 14; 16|] 

Tip

The function Array.zeroCreate creates an array with five values set to 0. There are many different ways to enter the items. It is important to remember that the value between square brackets refers to the position in zero-based.

let a = myArr2.[1] // 2 
let b = myArr2.[2..] // 3,4,5 
let c = myArr2.[..2] // 1,2,3 
let d = multiArr.[1] // [|(1, 1)|] 
 
myArr2.[1 ] <- 3 // to change a value 

List, in F#, is an ordered and immutable series of elements of the same type. The syntax is not very different other than the keywords used for declaration; list uses [ ] and array uses [| |]:

let myList2 = [ 1 .. 5 ] 
let myList3 = [ for i in 1 .. 5 -> i * i ] 
let list = [0..2..16] 
let a = myList2.[1] // 2 
let b = myList2.[2..] // 3,4,5 
let c = myList2.[..2] // 1,2,3 

A sequence can be easily confused with list because both the collections are used to represent an ordered and huge series of elements, all of the same type. However, sequences, unlike lists, are useful if you do not want to use all the elements of the collection.

Elements can represent numerous data structures and can be managed in many different ways, for example, through some operations, such as grouping, counting, and extracting functions.

In F#, sequences are defined with the syntax seq<T> also known as IEnumerable<T>, as you can see in the following example:

let myseq1 = seq { 0 .. 10 .. 100 } 
let myseq2 = seq { for i in 1 .. 5 do yield i * i } 
let myList3 = Seq.init 5 (fun n -> n * 5) 

The real power of arrays, lists, and sequences is their ability to manipulate data using their methods and the possibility to concatenate methods through the pipe operator or others.

In the following example, you can notice how simple it is to transform data and type collection only with a few instructions using the type array, sequence, and list. In the end, values are printed:

[| 1 .. 100 |]  
    |> Seq.ofArray  
    |> Seq.map (fun x -> x * x)  
    |> List.ofSeq 
    |> List.iter (fun y -> printfn "the value is: %i " y) 

The code also suggests how essential it is to know collections and their methods. As you can see in the following table, there are so many methods and overloads of them that could be useful having on hand the table with all the possibilities (refer to complete table MSDN link https://msdn.microsoft.com/en-us/library/hh967652.aspx) . Take a look at the following table:

Function

Array

List

Seq

Description

append

Returns a new collection that contains the elements of the first collection, followed by elements of the second collection.

add

-

-

-

Returns a new collection with the element added.

average

Returns the average of the elements in the collection.

averageBy

Returns the average of the results of the provided function applied to each element.

blit

-

-

Copies a section of an array.

cache

-

-

Computes and stores elements of a sequence.

cast

-

-

Converts the elements to the specified type.

choose

Applies the given function f to each element x of the list. Returns the list that contains the results for each element where the function returns Some(f(x)).

collect

Applies the given function to each element of the collection, concatenates all the results, and returns the combined list.

compareWith

-

-

Compares two sequences by using the given comparison function element by element.

concat

Combines the given enumeration-of-enumerations as a single concatenated enumeration.

contains

-

-

-

Returns true if the set contains the specified element.

containsKey

-

-

-

Tests whether an element is in the domain of a map.

count

-

-

-

Returns the number of elements in the set.

countBy

-

-

Applies a key generating function to each element of a sequence, and returns a sequence that yields unique keys and their number of occurrences in the original sequence.

copy

-

Copies the collection.

create

-

-

Creates an array of whole elements that are all initially the given value.

delay

-

-

Returns a sequence that's built from the given delayed specification of a sequence.

difference

-

-

-

Returns a new set with the elements of the second set removed from the first set.

distinct

Returns a sequence that contains no duplicate entries according to generic hash and equality comparisons on the entries. If an element occurs multiple times in the sequence, later occurrences are discarded.

distinctBy

Returns a sequence that contains no duplicate entries according to the generic hash and equality comparisons on the keys that the given key generating function returns. If an element occurs multiple times in the sequence, later occurrences are discarded.

empty

Creates an empty collection.

exists

Tests whether any element of the sequence satisfies the given predicate.

exists2

-

Tests whether any pair of corresponding elements of the input sequences satisfies the given predicate.

fill

Sets a range of elements of the array to the given value.

filter

Returns a new collection that contains only the elements of the collection for which the given predicate returns true.

find

Returns the first element for which the given function returns true. Returns KeyNotFoundException if no such element exists.

findIndex

Returns the index of the first element in the array that satisfies the given predicate. Raises KeyNotFoundException if no element satisfies the predicate.

findKey

-

-

-

Evaluates the function on each mapping in the collection, and returns the key for the first mapping where the function returns true. If no such element exists, this function raises KeyNotFoundException.

fold

Applies a function to each element of the collection, threading an accumulator argument through the computation. If the input function is f and the elements are i0...iN, this function will compute f (...(f s i0)...) iN.

fold2

-

Applies a function to the corresponding elements of two collections, threading an accumulator argument through the computation. The collections must have identical sizes. If the input function is f and the elements are i0...iN and j0...jN, this function will compute f (...(f s i0 j0)...) iN jN.

foldBack

-

Applies a function to each element of the collection, threading an accumulator argument through the computation. If the input function is f and the elements are i0...iN, this function will compute f i0 (...(f iN s)).

foldBack2

-

Applies a function to the corresponding elements of two collections, threading an accumulator argument through the computation. The collections must have identical sizes. If the input function is f and the elements are i0...iN and j0...jN, this function will compute f i0 j0 (...(f iN jN s)).

forall

Tests whether all the elements of the collection satisfy the given predicate.

forall2

Tests whether all the corresponding elements of the collection satisfy the given predicate pairwise.

get / nth

Returns an element from the collection given its index.

head

-

Returns the first element of the collection.

init

Creates a collection given the dimension and a generator function to compute the elements.

initInfinite

-

-

Generates a sequence that, when iterated, returns successive elements by calling the given function.

intersect

-

-

-

Computes the intersection of two sets.

intersectMany

-

-

-

Computes the intersection of a sequence of sets. The sequence must not be empty.

isEmpty

Returns true if the collection is empty.

isProperSubset

-

-

-

Returns true if all the elements of the first set are in the second set, and at least one element of the second set isn't in the first set.

isProperSuperset

-

-

-

Returns true if all the elements of the second set are in the first set, and at least one element of the first set isn't in the second set.

isSubset

-

-

-

Returns true if all the elements of the first set are in the second set.

isSuperset

-

-

-

Returns true if all the elements of the second set are in the first set.

iter

Applies the given function to each element of the collection.

iteri

Applies the given function to each element of the collection. The integer that's passed to the function indicates the index of the element.

iteri2

-

Applies the given function to a pair of elements that are drawn from matching indices in two arrays. The integer that's passed to the function indicates the index of the elements. The two arrays must have the same length.

iter2

Applies the given function to a pair of elements that are drawn from matching indices in two arrays. The two arrays must have the same length.

length

Returns the number of elements in the collection.

map

Builds a collection whose elements are the results of applying the given function to each element of the array.

map2

Builds a collection whose elements are the results of applying the given function to the corresponding elements of the two collections pairwise. The two input arrays must have the same length.

map3

-

-

Builds a collection whose elements are the results of applying the given function to the corresponding elements of the three collections simultaneously.

mapi

Builds an array whose elements are the results of applying the given function to each element of the array. The integer index that's passed to the function indicates the index of the element that's being transformed.

mapi2

-

Builds a collection whose elements are the results of applying the given function to the corresponding elements of the two collections pairwise, also passing the index of the elements. The two input arrays must have the same length.

max

Returns the greatest element in the collection, compared by using the max operator.

maxBy

Returns the greatest element in the collection, compared by using max on the function result.

maxElement

-

-

-

Returns the greatest element in the set according to the ordering that's used for the set.

min

Returns the least element in the collection, compared by using the min operator.

minBy

Returns the least element in the collection, compared by using the min operator on the function result.

minElement

-

-

-

Returns the lowest element in the set according to the ordering that's used for the set.

ofArray

-

Creates collection that contains the same elements as the given array.

ofList

-

Creates collection that contains the same elements as the given list.

ofSeq

-

Creates collection that contains the same elements as the given sequence.

pairwise

-

-

Returns a sequence of each element in the input sequence and its predecessor except for the first element, which is returned only as the predecessor of the second element.

partition

-

Splits the collection into two collections. The first collection contains the elements for which the given predicate returns true, and the second collection contains the elements for which the given predicate returns false.

permute

-

Returns an array with all the elements permuted according to the specified permutation.

pick

Applies the given function to successive elements, returning the first result where the function returns Some. If the function never returns Some, KeyNotFoundException will be raised.

readonly

-

-

Creates a sequence object that delegates to the given sequence object. This operation ensures that a type cast can't rediscover and mutate the original sequence. For example, if given an array, the returned sequence will return the elements of the array, but you can't cast the returned sequence object to an array.

reduce

Applies a function to each element of the collection, threading an accumulator argument through the computation. This function starts by applying the function to the first two elements, passes this result into the function along with the third element, and so on. The function returns the final result.

reduceBack

-

Applies a function to each element of the collection, threading an accumulator argument through the computation. If the input function is f and the elements are i0...iN, this function will compute f i0 (...(f iN-1 iN)).

remove

-

-

-

Removes an element from the domain of the map. No exception is raised if the element isn't present.

replicate

-

-

Creates a list of a specified length with every element set to the given value.

rev

-

Returns a new list with the elements in reverse order.

scan

Applies a function to each element of the collection, threading an accumulator argument through the computation. This operation applies the function to the second argument and the first element of the list. The operation then passes this result into the function along with the second element and so on. Finally, the operation returns the list of intermediate results and the final result.

scanBack

-

Resembles the foldBack operation but returns both the intermediate and final results.

singleton

-

-

Returns a sequence that yields only one item.

set

-

-

Sets an element of an array to the specified value.

skip

-

-

Returns a sequence that skips N elements of the underlying sequence and then yields the remaining elements of the sequence.

skipWhile

-

-

Returns a sequence that, when iterated, skips elements of the underlying sequence while the given predicate returns true and then yields the remaining elements of the sequence.

sort

Sorts the collection by element value. Elements are compared using compare.

sortBy

Sorts the given list by using keys that the given projection provides. Keys are compared using compare.

sortInPlace

-

-

Sorts the elements of an array by mutating it in place and using the given comparison function. Elements are compared by using compare.

sortInPlaceBy

-

-

Sorts the elements of an array by mutating it in place and using the given projection for the keys. Elements are compared by using compare.

sortInPlaceWith

-

-

Sorts the elements of an array by mutating it in place and using the given comparison function as the order.

sortWith

-

Sorts the elements of collection using the given comparison function as the order and returning a new collection.

sub

-

-

Builds an array that contains the given subrange that's specified by starting index and length.

sum

Returns the sum of the elements in the collection.

sumBy

Returns the sum of the results that are generated by applying the function to each element of the collection.

tail

-

-

Returns the list without its first element.

take

-

-

Returns the elements of the sequence up to a specified count.

takeWhile

-

-

Returns a sequence that, when iterated, yields the elements of the underlying sequence while the given predicate returns true and then returns no more elements.

toArray

-

Creates an array from the given collection.

toList

-

Creates a list from the given collection.

toSeq

-

Creates a sequence from the given collection.

truncate

-

-

Returns a sequence that, when enumerated, returns no more than N elements.

tryFind

Searches for an element that satisfies a given predicate.

tryFindIndex

Searches for the first element that satisfies a given predicate and returns the index of the matching element or None if no such element exists.

tryFindKey

-

-

-

Returns the key of the first mapping in the collection that satisfies the given predicate or returns None if no such element exists.

tryPick

Applies the given function to successive elements, returning the first result where the function returns something for some value. If no such element exists, the operation will return None.

unfold

-

-

Returns a sequence that contains the elements that the given computation generates.

union

-

-

-

Computes the union of the two sets.

unionMany

-

-

-

Computes the union of a sequence of sets.

unzip

Splits a list of pairs into two lists.

unzip3

Splits a list of triples into three lists.

windowed

-

-

Returns a sequence that yields the sliding windows of containing elements that are drawn from the input sequence. Each window is returned as a fresh array.

zip

Combines the two collections into a list of pairs. The two lists must have equal lengths.

zip3

Combines the three collections into a list of triples. The lists must have equal lengths.

In addition, in the next paragraph, you will see how to apply a case condition for each possibility of interrogation, manipulation, or extrapolation data.

..................Content has been hidden....................

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