Introduction to F#
Basic Introduction to the F# Programming Language
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
let myInt = 5
let myFloat = 3.14
let myString = "hello"
Lists can be defined with square brackets and elements are separated with semicolons
let list1 = [2;3;4;5]
We can create a new list with a prepended item using ::
and a concatenated list with @
let list2 = 1 :: list1 // [1;2;3;4;5]
let list3 = [0;1] @ list1 //[0;1;2;3;4;5]
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
let mutable changeableValue = 1
changeableValue <- 4
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
let refValue = ref 4
refValue := 1
let plus1 = !refValue + 1
Functions
Functions are defined with the let
keyword as well and the parameters are written after the name
let add x y =
x + y
The return value in the above function is the result of x + y
You can call the function using the following
add 1 6 // 7
A function can also be partially applied using the following
let add5 = add 5
add5 4 // 11
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
let greet () =
printfn "Hello"
greet()
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
let evens list =
let isEven x = x%2 = 0
List.filter isEven list
evens [1;2;3;4;5;6;7;8] // [2;4;6;8]
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
let evenFilter =
let isEven x = x%2 = 0
List.filter isEven
evenFilter [1;2;3;4;5;6;7;8] // [2;4;6;8]
Parenthesis can be used to specify the order in which functions should be applied
let sumOfSquares list =
let square x = x*x
List.sum (List.map square list)
sumOfSquares [1..10]
Alternatively, if you want to pass the output from one function into another, you can also use pipes which are done using |>
let sumOfSquaresPiped list =
let square x = x*x
list |> List.map square |> List.sum
sumOfSquaresPiped [1..10]
Or over multiple lines with:
let sumOfSquaresPiped list =
let square x = x*x
list
|> List.map square
|> List.sum
sumOfSquaresPiped [1..10]
The square
function can also be defined as a lambda
or anonymous function
using the fun
keyword
let sumOfSquaresLambda list =
list
|> List.map (fun x -> x*x)
|> List.sum
sumOfSquaresLambda [1..10]
Modules
Functions can be grouped as Modules using the module
keyword, with additional functions/variables defined inside of them using the let
module LocalModule =
let age = 5
let printName name =
printfn "My name is %s" name
module Math =
let add x y =
x + y
LocalModule.printName "John"
printfn "%i" (LocalModule.Math.add 1 3)
printfn "Age is %i" LocalModule.age
Modules can also include private
properties and methods, these can be defined with the private
keyword
module PrivateStuff =
let private age = 5
let printAge () =
printfn "Age is: %i" age
// PrivateStuff.age // this will not work
PrivateStuff.printAge()
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
module ExternalModule
let printName name =
printfn "My name is %s, - from ExternalModule" name
module Math =
let add x y =
x + y
module MoreMath =
let subtract x y =
x - y
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
#load "ExternalModule.fs"
ExternalModule.printName "Jeff"
ExternalModule.Math.add 1 3
If you want to expose the module contents you can do this with the open
keyword
open ExternalModule
printName "John"
Math.add 1 5
You can also do the same as above to open internal modules
open ExternalModule.Math
add 1 6
open MoreMath
subtract 5 1
Modules in which submodules/types make use of one another need to have the parent module defined using the rec
keyword as well
module rec RecursiveModule
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
)
let patternMatch x =
match x with
| "a" -> printfn "x is a"
| "b" -> printfn "x is b"
| _ -> printfn "x is something else"
patternMatch "a" // x is a
patternMatch "c" // x is something else
There is also the Some
and None
are like Nullable wrappers
let isInputNumber input =
match input with
| Some i -> printfn "input is an int: %d" i
| None -> printfn "input is missing"
isInputNumber (Some 5)
isInputNumber None
Complex Data Types
Tuples
Tuples are sets of variables, they are separated by commas
let twoNums = 1,2
let threeStuff = false,"a",2
Record Types
Record Types are defined with named fields separated by ;
type Person = { First:string; Last:string }
let john = {First="John"; Last="Test"} // Person
Union Types
Union Types have choices separated by `|'
type Temp =
| DegreesC of float
| DegreesF of int
let tempC = DegreesC 23.7
let tempF = DegreesF 64
Types can also be combined recursively such as:
type Employee =
| Worker of Person
| Manager of Employee list
let jeff = {First="Jeff"; Last="Smith"}
let workerJohn = Worker john
let workerJeff = Worker jeff
let johnny = Manager [workerJohn;workerJeff]
Printing
Printing can be done using the printf
and printfn
functions which are similar to Console.Write
and Console.WriteLine
functions in C#
printfn "int: %i, float: %f, bool: %b" 1 2.0 true
printfn "string: %s, generic: %A" "Hello" [1;3;5]
printfn "tuple: %A, Person: %A, Temp: %A, Employee: %A, Manager: %A" threeStuff john tempC workerJeff johnny
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:
type Person = { First:string; Last:string }
Or a Union
type that is a choice between two other types
type Temp =
| DegreesC of float
| DegreesF of int
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:
match myBool with
| true -> // do stuff
| false -> // do other stuff
A switch
:
match myNum with
| 1 -> // some stuff
| 2 -> // some other stuff
| _ -> // other other stuff
loops
are generally done using recursion like the following
match myList with
| [] -> // do something for the empty case
| first::rest ->
// do something with the first element
// recursively call the function
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
type Thing =
| Cat of age:int * colour:string
| Potato of isCooked:bool
let whatIsIt thing =
match thing with
| Cat (age, colour) -> printfn "This is a %s cat, it is %i years old" colour age
| Potato isCooked ->
let whatPotato = printfn "This is a %s potato"
match isCooked with
| true -> whatPotato "Cooked"
| false -> whatPotato "Raw"
Cat(3,"white")
|> whatIsIt
Potato(true)
|> whatIsIt