Building functional programs with F#

Functional programming features, F# basics, properties, functions & more

0
1651
22 min read

Functional programming treats programs as mathematical expressions and evaluates expressions. It focuses on functions and constants, which don’t change, unlike variables and states. Functional programming solves complex problems with simple code; it is a very efficient programming technique for writing bug-free applications; for example, the null exception can be avoided using this technique.

In today’s tutorial, we will learn how to build functional programs with F# that leverage .NET Core.

Here are some rules to understand functional programming better:

  • In functional programming, a function’s output never gets affected by outside code changes and the function always gives the same result for the same parameters. This gives us confidence in the function’s behavior that it will give the expected result in all the scenarios, and this is helpful for multithread or parallel programming.
  • In functional programming, variables are immutable, which means we cannot modify a variable once it is initialized, so it is easy to determine the value of a variable at any given point at program runtime.
  • Functional programming works on referential transparency, which means it doesn’t use assignment statements in a function. For example, if a function is assigning a new value to a variable such as shown here:
Public int sum(x)
{
x = x + 20 ;
return x;
}

This is changing the value of x, but if we write it as shown here:


Public int sum(x)
{
return x + 20 ;
}

This is not changing the variable value and the function returns the same result.

  • Functional programming uses recursion for looping. A recursive function calls itself and runs till the condition is satisfied.

Functional programming features

Let’s discuss some functional programming features:

  • Higher-order functions
  • Purity
  • Recursion
  • Currying
  • Closure
  • Function composition

Higher-order functions (HOF)

One function can take an input argument as another function and it can return a function. This originated from calculus and is widely used in functional programming. An order can be determined by domain and range of order such as order 0 has no function data and order 1 has a domain and range of order 0, if the order is higher than 1, it is called a higher-order function. For example, the ComplexCalc function takes another function as input and returns a different function as output:

open System
let sum y = x+x
let divide y = x/x
Let ComplexCalc func = (func 2)
Printfn(ComplexCalc sum) // 4
Printfn(ComplexCalc divide) //1

In the previous example, we created two functions, sum and divide. We pass these two functions as parameters to the ComplexCalc function, and it returns a value of 4 and 1, respectively.

Purity

In functional programming, a function is referred to as a pure function if all its input arguments are known and all its output results are also well known and declared; or we can say the input and output result has no side-effects. Now, you must be curious to know what the side-effect could be, let’s discuss it.

Let’s look at the following example:

Public int sum(int x)
{
return x+x; 
}

In the previous example, the function sum takes an integer input and returns an integer value and predefined result. This kind of function is referred to as a pure function. Let’s investigate the following example:

Public void verifyData()
{
  Employee emp = OrgQueue.getEmp();
  If(emp != null)
  {
    ProcessForm(emp);
  }
}

In the preceding example, the verifyData() function does not take any input parameter and does not return anything, but this function is internally calling the getEmp() function so verifyData() depends on the getEmp() function.

If the output of getEmp() is not null, it calls another function, called ProcessForm() and we pass the getEmp() function output as input for ProcessForm(emp). In this example, both the functions, getEmp() and ProcessForm(), are unknown at the verifyData() function level call, also emp is a hidden value. This kind of program, which has hidden input and output, is treated as a side-effect of the program. We cannot understand what it does in such functions. This is different from encapsulation; encapsulation hides the complexity but in such function, the functionality is not clear and input and output are unreliable. These kinds of function are referred to as impure functions.

Let’s look at the main concepts of pure functions:

  • Immutable data: Functional programming works on immutable data, it removes the side-effect of variable state change and gives a guarantee of an expected result.
  • Referential transparency: Large modules can be replaced by small code blocks and reuse any existing modules. For example, if a = b*c and d = b*c*e then the value of d can be written as d = a*e.
  • Lazy evaluation: Referential transparency and immutable data give us the flexibility to calculate the function at any given point of time and we will get the same result because a variable will not change its state at any time.

Recursion

In functional programming, looping is performed by recursive functions. In F#, to make a function recursive, we need to use the rec keyword. By default, functions are not recursive in F#, we have to rectify this explicitly using the rec keyword. Let’s take an example:

let rec summation x = if x = 0 then 0 else x + summation(x-1)
printfn "The summation of first 10 integers is- %A" (summation 10)

In this code, we used the keyword rec for the recursion function and if the value passed is 0, the sum would be 0; otherwise it will add x + summation(x-1), like 1+0 then 2+1 and so on. We should take care with recursion because it can consume memory heavily.

Currying

This converts a function with multiple input parameter to a function which takes one parameter at a time, or we can say it breaks the function into multiple functions, each taking one parameter at a time. Here is an example:

int sum = (a,b) => a+b
int sumcurry = (a) =>(b) => a+b
sumcurry(5)(6) // 11
int sum8 = sumcurry(8) // b=> 8+b
sum8(5) // 13

Closure

Closure is a feature which allows us to access a variable which is not within the scope of the current module. It is a way of implementing lexically scoped named binding, for example:

int add = x=> y=> x+y
int addTen = add(10)
addTen(5) // this will return 15

In this example, the add() function is internally called by the addTen() function. In an ideal world, the variables x and y should not be accessible when the add() function finishes its execution, but when we are calling the function addTen(), it returns 15. So, the state of the function add() is saved even though code execution is finished, otherwise, there is no way of knowing the add(10) value, where x = 10. We are able to find the value of x because of lexical scoping and this is called closure.

Function composition

As we discussed earlier in HOF, function composition means getting two functions together to create a third new function where the output of a function is the input of another function.

There are n number of functional programming features. Functional programming is a technique to solve problems and write code in an efficient way. It is not language-specific, but many languages support functional programming. We can also use non-functional languages (such as C#) to write programs in a functional way. F# is a Microsoft programming language for concise and declarative syntax.

Getting started with F#

In this section, we will discuss F# in more detail.

Classes

Classes are types of object which can contain functions, properties, and events. An F# class must have a parameter and a function attached to a member. Both properties and functions can use the member keyword. The following is the class definition syntax:

type [access-modifier] type-name [type-params] [access-modifier] (parameter-list) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
 [ end ]
// Mutually recursive class definitions:

type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...

Let’s discuss the preceding syntax for class declaration:

  • type: In the F# language, class definition starts with a type keyword.
  • access-modifier: The F# language supports three access modifiers—publicprivate, and internal. By default, it considers the public modifier if no other access modifier is provided. The Protected keyword is not used in the F# language, and the reason is that it will become object-oriented rather than functional programming. For example, F# usually calls a member using a lambda expression and if we make a member type protected and call an object of a different instance, it will not work.
  • type-name: It is any of the previously mentioned valid identifiers; the default access modifier is public.
  • type-params: It defines optional generic type parameters.
  • parameter-list: It defines constructor parameters; the default access modifier for the primary constructor is public.
  • identifier: It is used with the optional as keyword, the as keyword gives a name to an instance variable which can be used in the type definition to refer to the instance of the type.
  • Inherit: This keyword allows us to specify the base class for a class.
  • let-bindings: This is used to declare fields or function values in the context of a class.
  • do-bindings: This is useful for the execution of code to create an object
  • member-list: The member-list comprises extra constructors, instance and static method declarations, abstract bindings, interface declarations, and event and property declarations.

Here is an example of a class:

type StudentName(firstName,lastName) = 
member this.FirstName = firstName
member this.LastName = lastName

In the previous example, we have not defined the parameter type. By default, the program considers it as a string value but we can explicitly define a data type, as follows:

type StudentName(firstName:string,lastName:string) = 
member this.FirstName = firstName
member this.LastName = lastName
Constructor of a class

In F#, the constructor works in a different way to any other .NET language. The constructor creates an instance of a class. A parameter list defines the arguments of the primary constructor and class. The constructor contains let and do bindings, which we will discuss next. We can add multiple constructors, apart from the primary constructor, using the new keyword and it must invoke the primary constructor, which is defined with the class declaration. The syntax of defining a new constructor is as shown:

new (argument-list) = constructor-body

Here is an example to explain the concept. In the following code, the StudentDetail class has two constructors: a primary constructor that takes two arguments and another constructor that takes no arguments:

type StudentDetail(x: int, y: int) =
do printfn "%d %d" x y
new() = StudentDetail(0, 0)
A let and do binding

let and do binding creates the primary constructor of a class and runs when an instance of a class is created.

A function is compiled into a member if it has a let binding. If the let binding is a value which is not used in any function or member, then it is compiled into a local variable of a constructor; otherwise, it is compiled into a field of the class.

The do expression executes the initialized code. As any extra constructors always call the primary constructor, let and do bindings always execute, irrespective of which constructor is called.

Fields that are created by let bindings can be accessed through the methods and properties of the class, though they cannot be accessed from static methods, even if the static methods take an instance variable as a parameter:

type Student(name) as self =
    let data = name
    do
        self.PrintMessage()
        member this.PrintMessage() = printf " Student name is %s" data
Generic type parameters

F# also supports a generic parameter type. We can specify multiple generic type parameters separated by a comma. The syntax of a generic parameter declaration is as follows:

type MyGenericClassExample<'a> (x: 'a) =
   do printfn "%A" x

The type of the parameter infers where it is used. In the following code, we call the MyGenericClassExample method and pass a sequence of tuples, so here the parameter type became a sequence of tuples:

let g1 = MyGenericClassExample( seq { for i in 1 .. 10 -> (i, i*i) } )

Properties

Values related to an object are represented by properties. In object-oriented programming, properties represent data associated with an instance of an object. The following snippet shows two types of property syntax:

// Property that has both get and set defined.
[ attributes ]
[ static ] member [accessibility-modifier] [self- identifier.]PropertyName
with [accessibility-modifier] get() =
get-function-body
and [accessibility-modifier] set parameter =
set-function-body

// Alternative syntax for a property that has get and set.

[ attributes-for-get ]
[ static ] member [accessibility-modifier-for-get] [self-identifier.]PropertyName =
get-function-body
[ attributes-for-set ]
[ static ] member [accessibility-modifier-for-set] [self- 
identifier.]PropertyName
with set parameter =
set-function-body

There are two kinds of property declaration:

  • Explicitly specify the value: We should use the explicit way to implement the property if it has non-trivial implementation. We should use a member keyword for the explicit property declaration.
  • Automatically generate the value: We should use this when the property is just a simple wrapper for a value.

There are many ways of implementing an explicit property syntax based on need:

  • Read-only: Only the get() method
  • Write-only: Only the set() method
  • Read/write: Both get() and set() methods

An example is shown as follows:

// A read-only property.
member this.MyReadOnlyProperty = myInternalValue
// A write-only property.
member this.MyWriteOnlyProperty with set (value) = myInternalValue <- value
// A read-write property.
member this.MyReadWriteProperty
    with get () = myInternalValue
    and set (value) = myInternalValue <- value

Backing stores are private values that contain data for properties. The keyword, member val instructs the compiler to create backing stores automatically and then gives an expression to initialize the property. The F# language supports immutable types, but if we want to make a property mutable, we should use get and set. As shown in the following example, the MyClassExample class has two properties: propExample1 is read-only and is initialized to the argument provided to the primary constructor, and propExample2 is a settable property initialized with a string value ".Net Core 2.0":

type MyClassExample(propExample1 : int) =
member val propExample1 = property1
member val propExample2 = ".Net Core 2.0" with get, set
Automatically implemented properties don’t work efficiently with some libraries, for example, Entity Framework. In these cases, we should use explicit properties.

Static and instance properties

We can further categorize properties as static or instance properties. Static, as the name suggests, can be invoked without any instance. The self-identifier is neglected by the static property while it is necessary for the instance property. The following is an example of the static property:

static member MyStaticProperty
    with get() = myStaticValue
    and set(value) = myStaticValue <- value

Abstract properties

Abstract properties have no implementation and are fully abstract. They can be virtual. It should not be private and if one accessor is abstract all others must be abstract. The following is an example of the abstract property and how to use it:

// Abstract property in abstract class.
// The property is an int type that has a get and
// set method
[<AbstractClass>]
type AbstractBase() =
   abstract Property1 : int with get, set
// Implementation of the abstract property
type Derived1() =
inherit AbstractBase()
let mutable value = 10
override this.Property1 with get() = value and set(v : int) = value 
<- v

// A type with a "virtual" property.
type Base1() =
let mutable value = 10
abstract Property1 : int with get, set
default this.Property1 with get() = value and set(v : int) = value 
<- v

// A derived type that overrides the virtual property
type Derived2() =
inherit Base1()
let mutable value2 = 11
override this.Property1 with get() = value2 and set(v) = value2 <- v

Inheritance and casts

In F#, the inherit keyword is used while declaring a class. The following is the syntax:

type MyDerived(...) = inherit MyBase(...)

In a derived class, we can access all methods and members of the base class, but it should not be a private member. To refer to base class instances in the F# language, the base keyword is used.

Virtual methods and overrides

 In F#, the abstract keyword is used to declare a virtual member. So, here we can write a complete definition of the member as we use abstract for virtual. F# is not similar to other .NET languages. Let’s have a look at the following example:

type MyClassExampleBase() =
   let mutable x = 0
   abstract member virtualMethodExample : int -> int
   default u. virtualMethodExample (a : int) = x <- x + a; x
type MyClassExampleDerived() =
inherit MyClassExampleBase ()
override u. virtualMethodExample (a: int) = a + 1

In the previous example, we declared a virtual method, virtualMethodExample, in a base class, MyClassExampleBase, and overrode it in a derived class, MyClassExampleDerived.

Constructors and inheritance

An inherited class constructor must be called in a derived class. If a base class constructor contains some arguments, then it takes parameters of the derived class as input. In the following example, we will see how derived class arguments are passed in the base class constructor with inheritance:

type MyClassBase2(x: int) =
   let mutable z = x * x
   do for i in 1..z do printf "%d " i
type MyClassDerived2(y: int) =
   inherit MyClassBase2(y * 2)
   do for i in 1..y do printf "%d " i

If a class has multiple constructors, such as new(str) or new(), and this class is inherited in a derived class, we can use a base class constructor to assign values. For example, DerivedClass, which inherits BaseClass, has new(str1,str2), and in place of the first string, we pass inherit BaseClass(str1). Similarly, for blank, we wrote inherit BaseClass(). Let’s explore the following example in more detail:

type BaseClass =
    val string1 : string
    new (str) = { string1 = str }
    new () = { string1 = "" }
type DerivedClass =
inherit BaseClass

val string2 : string
new (str1, str2) = { inherit BaseClass(str1); string2 = str2 }
new (str2) = { inherit BaseClass(); string2 = str2 }

let obj1 = DerivedClass("A", "B")
let obj2 = DerivedClass("A")

Functions and lambda expressions

A lambda expression is one kind of anonymous function, which means it doesn’t have a name attached to it. But if we want to create a function which can be called, we can use the fun keyword with a lambda expression. We can pass the kind parameter in the lambda function, which is created using the fun keyword. This function is quite similar to a normal F# function. Let’s see a normal F# function and a lambda function:

// Normal F# function
let addNumbers a b = a+b
// Evaluating values
let sumResult = addNumbers 5 6 
// Lambda function and evaluating values
let sumResult = (fun (a:int) (b:int) -> a+b) 5 6
// Both the function will return value sumResult = 11

Handling data – tuples, lists, record types, and data manipulation

F# supports many kind data types, for example:

  • Primitive typesboolintfloatstring values.
  • Aggregate typeclassstructunionrecord, and enum
  • Arrayint[]int[ , ], and float[ , , ]
  • Tupletype1 * type2 * like (a,1,2,true) type is—char * int * int * bool
  • Genericlist<’x>, dictionary < ’key, ’value>

In an F# function, we can pass one tuple instead parameters of multiple parameters of different types. Declaration of a tuple is very simple and we can assign values of a tuple to different variables, for example:

let tuple1 = 1,2,3
// assigning values to variables , v1=1, v2= 2, v3=3

let v1,v2,v3 = tuple1

// if we want to assign only two values out of three, use “_” to skip the value. Assigned values: v1=1, //v3=3

let v1,_,v3 = tuple

In the preceding examples, we saw that tuple supports pattern matching. These are option types and an option type in F# supports the idea that the value may or not be present at runtime.

List

List is a generic type implementation. An F# list is similar to a linked list implementation in any other functional language. It has a special opening and closing bracket construct, a short form of the standard empty list ([ ]) syntax:

let empty = [] // This is an empty list of untyped type or we can say     //generic type. Here type is: 'a list
let intList = [10;20;30;40] // this is an integer type list

The cons operator is used to prepend an item to a list using a double colon cons(prepend,::). To append another list to one list, we use the append operator—@:

// prepend item x into a list
let addItem xs x = x :: xs 
let newIntList = addItem intList 50 // add item 50 in above list //“intlist”, final result would be- [50;10;20;30;40]

// using @ to append two list
printfn “%A” ([“hi”; “team”] @ [“how”;”are”;”you”])
// result – [“hi”; “team”; “how”;”are”;”you”]

Lists are decomposable using pattern matching into a head and a tail part, where the head is the first item in the list and the tail part is the remaining list, for example:

printfn "%A" newIntList.Head
printfn "%A" newIntList.Tail
printfn "%A" newIntList.Tail.Tail.Head
let rec listLength (l: 'a list) =
    if l.IsEmpty then 0
        else 1 + (listLength l.Tail)
printfn "%d" (listLength newIntList)

Record type

The classstructunionrecord, and enum types come under aggregate types. The record type is one of them, it can have n number of members of any individual type. Record type members are by default immutable but we can make them mutable. In general, a record type uses the members as an immutable data type. There is no way to execute logic during instantiation as a record type don’t have constructors. A record type also supports match expression, depending on the values inside those records, and they can also again decompose those values for individual handling, for example:

type Box = {width: float ; height:int }
let giftbox = {width = 6.2 ; height = 3 }

In the previous example, we declared a Box with float a value width and an integer height. When we declare giftbox, the compiler automatically detects its type as Box by matching the value types. We can also specify type like this:

let giftbox = {Box.width = 6.2 ; Box.height = 3 }

or

let giftbox : Box = {width = 6.2 ; height = 3 }

This kind of type declaration is used when we have the same type of fields or field type declared in more than one type. This declaration is called a record expression.

Object-oriented programming in F#

F# also supports implementation inheritance, the creation of object, and interface instances. In F#, constructed types are fully compatible .NET classes which support one or more constructors. We can implement a do block with code logic, which can run at the time of class instance creation. The constructed type supports inheritance for class hierarchy creation. We use the inherit keyword to inherit a class. If the member doesn’t have implementation, we can use the abstract keyword for declaration. We need to use the abstractClass attribute on the class to inform the compiler that it is abstract. If the abstractClass attribute is not used and type has all abstract members, the F# compiler automatically creates an interface type. Interface is automatically inferred by the compiler as shown in the following screenshot:

Interface

The override keyword is used to override the base class implementation; to use the base class implementation of the same member, we use the base keyword.

In F#, interfaces can be inherited from another interface. In a class, if we use the construct interface, we have to implement all the members in the interface in that class, as well. In general, it is not possible to use interface members from outside the class instance, unless we upcast the instance type to the required interface type.

To create an instance of a class or interface, the object expression syntax is used. We need to override virtual members if we are creating a class instance and need member implementation for interface instantiation:

type IExampleInterface = 
    abstract member IntValue: int with get
    abstract member HelloString: unit -> string
type PrintValues() = 
interface IExampleInterface with
member x.IntValue = 15
member x.HelloString() = sprintf "Hello friends %d" (x :> 
IExampleInterface).IntValue

let example = 
let varValue = PrintValues() :> IExampleInterface
{ new IExampleInterface with
member x.IntValue = varValue.IntValue
member x.HelloString() = sprintf "<b>%s</b>" 
(varValue.HelloString()) }

printfn "%A" (example.HelloString())

Exception handling

The exception keyword is used to create a custom exception in F#; these exceptions adhere to Microsoft best practices, such as constructors supplied, serialization support, and so on. The keyword raise is used to throw an exception. Apart from this, F# has some helper functions, such as failwith, which throws a failure exception at F# runtime, and invalidopinvalidarg, which throw the .NET Framework standard type invalid operation and invalid argument exception, respectively.

try/with is used to catch an exception; if an exception occurred on an expression or while evaluating a value, then the try/with expression could be used on the right side of the value evaluation and to assign the value back to some other value. try/with also supports pattern matching to check an individual exception type and extract an item from it. try/finally expression handling depends on the actual code block. Let’s take an example of declaring and using a custom exception:

exception MyCustomExceptionExample of int * string
   raise (MyCustomExceptionExample(10, "Error!"))

In the previous example, we created a custom exception called MyCustomExceptionExample, using the exception keyword, passing value fields which we want to pass. Then we used the raise keyword to raise exception passing values, which we want to display while running the application or throwing the exception. However, as shown here, while running this code, we don’t get our custom message in the error value and the standard exception message is displayed:

standard exception message

We can see in the previous screenshot that the exception message doesn’t contain the message that we passed. In order to display our custom error message, we need to override the standard message property on the exception type. We will use pattern matching assignment to get two values and up-cast the actual type, due to the internal representation of the exception object.

If we run this program again, we will get the custom message in the exception:

exception MyCustomExceptionExample of int * string
 with
        override x.Message = 
            let (MyCustomExceptionExample(i, s)) = upcast x
            sprintf "Int: %d Str: %s" i s

raise (MyCustomExceptionExample(20, “MyCustomErrorMessage!”))

Now, we will get the following error message:

error message

In the previous screenshot, we can see our custom message with integer and string values included in the output. We can also use the helper function, failwith, to raise a failure exception, as it includes our message as an error message, as follows:

failwith "An error has occurred"

The preceding error message can be seen in the following screenshot:

System exception

Here is a detailed exception screenshot:

exception screen

An example of the invalidarg helper function follows. In this factorial function, we are checking that the value of x is greater than zero. For cases where x is less than 0, we call invalidarg, pass x as the parameter name that is invalid, and then some error message saying the value should be greater than 0. The invalidarg helper function throws an invalid argument exception from the standard system namespace in .NET:

let rec factorial x = 
if x < 0 then invalidArg "x" "Value should be greater than zero"
match x with 
| 0 -> 1
| _ -> x * (factorial (x - 1))

To summarize, we discussed functional programming and its features, such as higher-order
functions, purity, lazy evaluation and how to write functions and lambda expressions in F#, exception handling, and so on.

You enjoyed an excerpt from a book written by Rishabh Verma and Neha Shrivastava, titled  .NET Core 2.0 By Example. This book will give a detailed walkthrough on functional programming with F# and .NET Core from scratch.

.Net Core 2.0 by example

Read Next:

What is functional reactive programming?

Functional Programming in C#

 

LEAVE A REPLY

Please enter your comment!
Please enter your name here