F# is a functional programming language. Functional programming treats programs as mathematical expressions. It focuses on functions and constants that don't change, rather than variables and states. F# is a Microsoft programming language for concise and declarative syntax. Let's begin with a brief history of how this language came into existence. The first attempt at functional programming was Haskell .NET. F# development began in 2005 and after that, various versions came along. At the time of writing this chapter, F# 4.1 is the latest version; it was released in March 2017. This comes with Visual Studio 2017 and supports .NET Core.
The F# language can be used for the following tasks:
- To solve mathematical problems
- For graphic design
- For financial modeling
- For compiler programming
- For CPU design
It is also used for CRUD applications, web pages GUI games, and other programs.
Keywords and their use in the F# language are outlined in the following table:
Keyword | Description |
| Indicates that either it has no implementation or is a virtual and has default implementation. |
| In verbose syntax, indicates the start of a code block. |
| Indicates an implementation of an abstract method; used together with an abstract method declaration to create a virtual method. |
| Used in conditional branching. A short form of else-if. |
| Used in type definitions and type extensions, indicates the end of a section of member definitions. In verbose syntax, used to specify the end of a code block that starts with the |
| Used to declare an exception type. |
| Used together with |
| Used in lambda expressions, and is also known as anonymous functions. |
| Used as a shorter alternative to the |
| Used to specify a base class or base interface. |
| Used to declare and implement interfaces. |
| Used to associate or bind a name to a value or function. |
| Used to declare a property or method in an object type. |
| Used to declare a variable; that is, a value that can be changed. |
| Used to implement a version of an abstract or virtual method that differs from the base version. |
| Used to indicate that a function is recursive. |
| Used in query expressions to specify what fields or columns to extract. Note that this is a contextual keyword, which means that it is not actually a reserved word and it only acts like a keyword in an appropriate context. |
| Used to indicate a method or property that can be called without an instance of a type, or a value member that is shared among all instances of a type. |
| Used to declare a structure type. Also used in generic parameter constraints. Used for OCaml compatibility in module definitions. |
| Used to declare a class, record, structure, discriminated union, enumeration type, unit of measure, or type abbreviation. |
| Used in a signature to indicate a value, or in a type to declare a member, in limited situations. |
| Used in a sequence expression to produce a value for a sequence. |
This reference is taken from the Microsoft official website, and a detailed explanation and description can be found at https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/keyword-reference.
In the F# language, we have two types of comments for a single line and for multiple lines. This is the same as C#. The following are the two types of comments:
Example: // returns an integer exit code
Example: (*Learn more about F# at http://fsharp.org *)
F# has a rich data type system. We can broadly classify them as:
- Integral types:
sbyte
,byte
,int16
,uint16
,int32
,uint32
,int64
, andbigint
- Floating point types:
float32
,float
, anddecimal
- Text types:
char
andstring
- Other types:
bool
These types are also referred to as fundamental primitive types in F#. Apart from these, F# has an exhaustive list of predefined types as well, such as lists, arrays, records, enumerations, tuples, units, sequences, and so on. It is recommended that a person learning F# goes through the official Microsoft documentation on F# at https://docs.microsoft.com/en-us/dotnet/fsharp/.
F# uses the let
keyword for the declaration of a variable, for example:
let square x = x*x
The compiler automatically detects this as a value type. If we pass a float value, the compiler will be able to understand that without declaring a data type. Variables in F# are immutable, so once a value is assigned to a variable, it can't be changed. They are compiled as static read-only properties.
The following example demonstrates this:
let x:int32 = 50 let y:int32 = 30 let z:int32 = x + y
Variables x
, y
, and z
are all of type int32
and are immutable, meaning their value cannot be changed.
Let's print their values. The syntax is as follows:
printfn "x: %i" x printfn "y: %i" y printfn "z: %i" z
After the preceding code executes, the result is as follows:
x: 50 y: 30 z: 80
Now, suppose we want to modify the value of x
from 50
to 60
and check that z
reflects the updated sum; we will write the code as:
let x = 60 let y = 30 let z = x + y
On executing this code, we get the following errors, and rightly so because x
and z
are immutable:
Duplicate definition of value 'x' Duplicate definition of value 'z'
The correct way of doing it would be to use mutable variables for the declaration, as shown here:
let mutable x = 60 let y = 30 //It's optional to make y mutable. let mutable z = x + y x <- 70 z <- x + y
On printing the values again, we will see:
x: 70 y: 30 z: 100
F# has the following operators:
- Arithmetic operators
- Comparison operators
- Boolean operators
- Bitwise operators
Let's discuss these operators in detail.
Arithmetic operators supported by the F# language are outlined in the following table. Assuming variable X = 10
and variable Y = 40
, we have the following expressions:
Operator | Description | Example |
| Adds two values |
|
| Subtracts the second value from the first |
|
| Multiplies both values |
|
| Divides two values |
|
| Modulus operator and gives the value of the remainder after an integer division |
|
| Exponentiation operator; raises one variable to the power of another |
|
The following table shows all the comparison operators supported by F# . These operators return true
or false
.
Let's take X = 20
and Y = 30
:
Operator | Description | Example |
| Verifies the values of two variables are equal; if not, then the condition becomes |
|
| Verifies the values of two variables are equal; if values are not equal then the condition becomes |
|
| Verifies the value of the left variable is greater than the value of the right variable; if not, then the condition becomes |
|
| Verifies the value of the left variable is less than the value of the right variable; if yes, then the condition becomes |
|
| Verifies the value of the left variable is greater than or equal to the value of the right variable; if not, then condition becomes |
|
| Verifies the value of the left variable is less than or equal to the value of the right variable; if yes, then the condition becomes |
|
The following table shows all the Boolean operators supported by the F# language. Let's take variable X
as true
and Y
as false
:
Operator | Description | Example |
| Boolean AND operator. If both the bool values are |
|
| Boolean OR operator. If either of the two bool values is |
|
| Boolean NOT operator. If the condition is |
|
Bitwise operators work on bits and perform bit-by-bit operations. The truth tables for &&&
(bitwise AND), |||
(bitwise OR), and ^^^
(bitwise exclusive OR) are shown as follows. In the following table, the first variable is X
and the second variable is Y
:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
It also supports ~~~
(Unary, effect of flipping bits) , <<<
(left shift operator), and >>>
(right shift operator).
The F# language has the following (if...else
and loop) types of decision-making statements.
The following table shows all the ways of implementing if
statements:
Statement | Description |
| An |
| An |
| An |
Nested | You can use one |
F# provides the following types of loop:
Loop type | Description |
| The |
| This form of |
| Repeats a statement or group of statements while the given condition is true. It tests the condition before executing the loop body. |
nested loops | We can use one or more loop inside any other |
F# functions act like variables. We can declare and use them in the same way as we use variables in C#. A function definition starts with the let
keyword, followed by the function name and parameters, a colon, its type, and the right-side expression, showing what the function does. The syntax is follows:
Let functionName parameters [ : returnType] = functionbody
In the preceding syntax:
functionName
is an identifier of the function.parameters
gives the list of parameters separated by spaces. We can also specify an explicit type for each parameter and if not specified, the compiler tends to presume it from the function body as variables.functionbody
comprises an expression, or a compound expression, which has number of expressions. The final expression in the function body is the return value.returnType
is a colon followed by a type and it is optional. If thereturnType
is not specified, then the compiler determines it from the final expression in the function body.
Have a look at the following example for our syntax:
let addValue (x : int) = 5 + x
A function can be called by passing the function name followed, by a space, and then arguments (if any) separated by spaces, as shown here:
let sum = addValue 3
We can perform many tasks using F# functions, some of which are as follows:
- We can create a new function and link that function with a type as it acts as a variable type:
let square x = x*x
- We can perform some calculations as well, such as:
let square x = x*x
- We can assign a value. Taking the same example:
let square x = x*x
- We can pass a function as a parameter to another function like this:
let squareValue = List.map square[1;2;3] // using square function
- We can return a function as a result of another function example:
let squareValue = List.map square[1;2;3]
The order of files in a project matters in an F# solution. The file used in any function should be placed above the file where the function is used, because F# has a forward-only model of compilation.
Unlike C#, where the file sequence doesn't matter, the sequencing of files does matter in F#. For example, consider that Program.fs
is using DotNetCorePrint.fs
. So, DotNetCorePrint.fs
should be placed above Program.fs
in the solution; otherwise, it will throw a compilation error. To move a file up or down, we can right-click on the file and select Move Up or the keys Alt + the up arrow to move the file. The ordering of the files in
Solution Explorer
can be seen in the following screenshot:
We are now going to see how to write and read in F#. To read and write into the console, we can use the following commands:
- To write:
System.Console.Write("Welcome!")
- To read:
System.Console.Read()
- To print:
printfn "Hello"
Let's compare F# with C#:
F# | C# |
The F# user is free from the obligation of defining types; for example: The compiler can identify (
| The C# user is bound to provide a type:
|
F# has immutable data, and the value never changes; for example:
In the preceding example, to add one more number, ( | C# has mutable as well as immutable data. Strings are immutable, the rest are all mutable, for example:
In the preceding example, we created a list and added a new item to the same list. The list gets modified. |
F# compiles code in an order. It is favoured for data processing and algorithmic computation. It doesn't work on visibility; it works sequentially.
| C# code works on visibility. The sequence doesn't matter. |
The order of files in a project matters in the F# solution. A file used in any method should be placed above the file where the method has been used.
| The order doesn't matter in C#.
|
F# has precise syntax; it focuses on what not and how; for example:
Right-click F# uses declarative syntax, not imperative syntax as in C#. F# helps us to minimize accidental complexity, meaning complexity that is not a part of the problem, but which we introduced as part of the solution, for example:
Quicksort smaller
| C# code implementation is more about how to implement. It sometimes increases unnecessary complexity as part of the solution to a problem. Quick sort has a very complex algorithm and a high chance of increasing accidental complexity. |