Introduction to F#
Basic Introduction to the F# Programming Language
Updated: 03 September 2023
Mostly based on the content here
Foreword: When creating a new F# project from Visual Studio do not check the
Dockerfile
option, this will result in the following error when trying to run the application in some casesA function labeled with the 'EntryPointAttribute' attribute must be the last declaration in the last file in the compilation sequence.
F# is a functional language that runs in the .NET Core ecosystem. It’s a weird language.
Install and Run
Before you can get started with F# you will need to install it, there are a few different options available depending on your choice of operating system and editor, these instructions can be found here
There are a few different styles that we can use to run F#, the simplest would be an F# script, which uses the .fsx
extension
You can run F# code using Visual Studio and starting a new F# project, console app or even just by creating a new F# script file, if running a .fsx
file you can highlight the code you want to run and clicking alt+enter
Alternatively you can run F# in Visual Studio Code using the same method as above with theIonide F# Language Support
extension
Syntax
F# is whitespace sensitive and uses indentation to denote code blocks
F# makes use of implicit typing however you can explicitly state types as well
Variables
Variables are immutable by default and are defined using the let
keyword.
Also, ideally they don’t vary
Lists can be defined with square brackets and elements are separated with semicolons
We can create a new list with a prepended item using ::
and a concatenated list with @
Mutable and Reference Values
You can create Mutable variables which would allow the value to be changed, this can be done using the mutable
keyword as follows
Note that in the above case the <-
operator is used to assign values to a mutable varia
Reference values are sort of like wrappers around mutable value. Defining them makes use of the ref
keyword, modifying them makes use of the:=
operator to assign values and !
to access values
Functions
Functions are defined with the let
keyword as well and the parameters are written after the name
The return value in the above function is the result of x + y
You can call the function using the following
A function can also be partially applied using the following
Which sets x
as 5
and returns a function for 5 + y
If a function does not have any input parameters, it should be defined using ()
to indicate that it is a function and must also be used with the ()
to actually apply the function and not just reference the variable
A function that returns only even numbers can be defined using the following function within a function and the List.filter
function which takes in a predicate
and a list
as inputs
We can write the above in the following form as well which returns a function that will filter out the even values in a list
Parenthesis can be used to specify the order in which functions should be applied
Alternatively, if you want to pass the output from one function into another, you can also use pipes which are done using |>
Or over multiple lines with:
The square
function can also be defined as a lambda
or anonymous function
using the fun
keyword
Modules
Functions can be grouped as Modules using the module
keyword, with additional functions/variables defined inside of them using the let
Modules can also include private
properties and methods, these can be defined with the private
keyword
You can define a module in a different file and can then import this into another file using the open
keyword. Note that there needs to be a top-level module which does not make use of the =
sign, but other internal modules do
If using a script, you will need to first load
the module to make the contents available, you can then use the values from the Module
using the name as an accessor. This will now essentially function as if the
If you want to expose the module contents you can do this with the open
keyword
You can also do the same as above to open internal modules
Modules in which submodules/types make use of one another need to have the parent module defined using the rec
keyword as well
Switch Statements
Switch statements can be used with the match ... with
keyword, |
to separate comparators, and ->
for the resulting statement. An _
is used to match anything (default
)
There is also the Some
and None
are like Nullable wrappers
Complex Data Types
Tuples
Tuples are sets of variables, they are separated by commas
Record Types
Record Types are defined with named fields separated by ;
Union Types
Union Types have choices separated by `|’
Types can also be combined recursively such as:
Printing
Printing can be done using the printf
and printfn
functions which are similar to Console.Write
and Console.WriteLine
functions in C#
Key Concepts
F# has four key concepts that are used when aproaching problems
Function Oriented
F# is a functional language and functions are first-class
entities and can be used as any other value/variable
This enables you to write code using functional composition in order to build complex functions out of basic functions
Expressions over Statements
F# prefers to make use of expressions instead of statements. Variables tend to be declared at the same time they are assigned and do not need to be ‘set-up’ for use, such as in the typical case of an if-else
statement
Algebraic Types
Types are based on the concept of algebraic types
where compound types are built out of their composition with other types
In the case of the below you would be creating a Product
type that is a combination of two strings, for example:
Or a Union
type that is a choice between two other types
Flow Control with Matching
Instead of making use of if ... else
, switch ... case
, for
, while
among others like most languages, F# uses patttern-matchin
using match ... with
to handle much of the functionality of the above
An example of an if-then
can be:
A switch
:
loops
are generally done using recursion like the following
Pattern Matching with Union Types
Union Types can also be used in matching and a function based on them can be used to correctly handle and apply the arguments, provided the return type is consistent