17 min read

F# is Microsoft’s purely functional programming language, that can be used along with the .NET Core framework. In this article, we will get introduced to F# to leverage .NET Core for our application development.

This article is extracted from the book, .NET Core 2.0 By Example, written by Rishabh Verma and Neha Shrivastava.

Basics of classes

Classes are types of object which can contain functions, properties, and events. An F# class must have a parameter and a function attached like 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—public, private, 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

A 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

There can be further categorization of 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 for 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 input 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 data types, for example:

  • Primitive types: bool, int, float, string values.
  • Aggregate typeclass, struct, union, record, and enum
  • Array: int[], int[ , ], and float[ , , ]
  • Tuple: type1 * type2 * like (a,1,2,true) type is—char * int * int * bool
  • Generic: list<’x>, dictionary < ’key, ’value>

In an F# function, we can pass one tuple instead 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 class, struct, union, record, 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:

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 invalidop, invalidarg, 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:

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:

exception error

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:

error

Here is a detailed exception screenshot:

exception settings

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))

By now, you should be pretty familiar with the F# programming language, to use in your application development, alongside C#.

If you found this tutorial helpful and you’re interested in learning more, head over to this book .NET Core 2.0 By Example, by Rishabh Verma and Neha Shrivastava.

Read Next:

.NET Core completes move to the new compiler – RyuJIT

Applying Single Responsibility principle from SOLID in .NET Core

Unit Testing in .NET Core with Visual Studio 2017 for better code quality

I'm a technology enthusiast who designs and creates learning content for IT professionals, in my role as a Category Manager at Packt. I also blog about what's trending in technology and IT. I'm a foodie, an adventure freak, a beard grower and a doggie lover.

LEAVE A REPLY

Please enter your comment!
Please enter your name here