This document provides an overview of the F# programming language. It discusses key features of F# such as immutability, pattern matching, discriminated unions, and automatic currying. It provides code examples demonstrating these features. It also describes how to get started with F# interactive and discusses resources for learning more about F#.
11. Getting started – F# interactive FSI
https://www.microsoft.com/net/download/core
dotnet new console -lang f# -o MyConsoleApp
cd MyConsoleApp
dotnet restore
code .
12. Create fsx file and start
executing code with f#
interactive!
Just highlight and press
Alt-Enter
13. The Delights
• Simplicity
• Immutability
• Pattern matching
• Encoding logic in types
• Automatic currying – all functions are one arg functions
• High level transformations and functional pipelines
15. Will this increment x ??
Evaluated in FSI…
val x : int = 2
val it : bool = false
let x = 2
x = x + 1
16. Specifying Mutability
Evaluated in FSI…
val mutable y : int = 3
val it : unit = ()
x <- x + 1 //won’t compile as x not mutable.
let mutable y = 2
y <- y + 1
17. Tuples
let p = ("Tom", 38)
fst p
Evaluated in FSI ….
val p : string * int = ("Tom", 38)
val it : string = "Tom"
18. Immutability – OO Classes
type Person(name:string) =
member p.Name = name
19. Immutability – Records
type Person = {
Name : string
Age : int }
let p = { Name = "Tom"; Age = 56 }
Evaluated in FSI…
type Person =
{Name: string;
Age: int;}
val p : Person = {Name = "Tom";
Age = 56;}
20. Evaluated in FSI…
val it : string = "Tom"
//won't compile as records are immutable.
p.Name <- "Jack"
p.Name
21. Immutable Collections
Evaluated in FSI…
val numbers : int list = [1; 2; 3; 4]
open FSharp.Collections
let numbers = [ 1; 2; 3; 4 ]
//won't compile becuase lists are immutable
numbers.[2] <- 7
22. Arrays still mutable
Evaluated in FSI…
val names : string [] = [|"tom"; "eleanor"|]
val it : string = "tom"
let names = [| "tom"; "eleanor" |]
names.[0]
names.[0] <- "Myles"
names.[0]
Evaluated in FSI…
val it : string = "Myles"
23. Pattern matching
Evaluated in FSI…
val person : string * int = ("Tom", 38)
val name : string = "Tom"
val age : int = 38
let person = ("Tom", 38)
let (name, age) = person
24. Match expressions
Evaluated in FSI…
val getName : 'a * 'b -> 'a
val person : string * int = ("Tom", 38)
val it : string = "Tom"
let getName person =
match person with
| (name, age) -> name
let person = ("Tom", 38)
getName person
25. Pattern matching on records
Evaluated in FSI…
type Person =
{Name: string;
Age: int;}
val p : Person = {Name = "Tom";
Age = 38;}
val personName : string = "Tom"
type Person = { Name : string; Age : int }
let p = { Name = "Tom"; Age = 38 }
let { Name = personName; Age = _ } = p
26. Pattern matching on lists
let l = [ 1; 2; 3 ]
1
2
3
[]
//l can be re-written as
let l = 1 :: 2 :: 3 :: []
27. Evaluated in FSI…
val l : int list = [1; 2; 3]
val getFirst : list:'a list -> 'a
val it : int = 1
let l = 1 :: 2 :: 3 :: []
let getFirst list =
match list with // warning if not all patterns covered
| x :: xs -> x
| [] -> failwith "cannot get first on an empty list"
// you wouldn't actually do this - this would return an option
instead - will cover this shortly.
getFirst l
28. Discriminated Unions
type Drummer = Drummer of string
type BassPlayer = BassPlayer of string
type SaxPlayer = SaxPlayer of string
type PianoPlayer = PianoPlayer of string
type JazzBand =
| PianoTrio of PianoPlayer * BassPlayer * Drummer
| SaxTrio of SaxPlayer * BassPlayer * Drummer
| Quartet of SaxPlayer * PianoPlayer * BassPlayer * Drummer
29. Simplified…
let getBandLeader (jazzband: JazzBand) =
match jazzband with
| PianoTrio(PianoPlayer(ppn), BassPlayer(bassPlayerName), Drummer(dn)) -> ppn
| SaxTrio(SaxPlayer(spn), BassPlayer(bpn), Drummer(dn)) -> spn
| Quartet(SaxPlayer(spn), PianoPlayer(name), BassPlayer(bpn), Drummer(dn)) -> spn
let getBandLeader (jazzband: JazzBand) =
match jazzband with
| PianoTrio(PianoPlayer(pianoPlayerName), _, _) -> pianoPlayerName
| SaxTrio(SaxPlayer(saxPlayerName), _, _) -> saxPlayerName
| Quartet(SaxPlayer(saxPlayerName), _, _, _) -> saxPlayerName
30. Making illegal states unrepresentable
From a different module ….
type Drummer = private Drummer of string
let createDrummer name =
// do some lookup to validate that this person is actually
// a drummer
let drummers = [ "Max Roache"; "Elvin Jones"; "Tony Williams" ]
if List.contains name drummers then
Some (Drummer name)
else None
// won't compile because the union case fields are not accessible
let drummer = Drummer("tom")
31. Making illegal states unrepresentable
Evaluate in FSI…
let drummer = createDrummer "tom"
val drummer : Drummer option = None
let drummer = createDrummer "Max Roache"
Evaluate in FSI…
val drummer : Drummer option = Some Drummer "Max Roache"
32. Pattern matching private data constructors
let (|Drummer|) (Drummer name) = Drummer(name)
let getBandLeader (jazzband: JazzBand) =
match jazzband with
| PianoTrio(PianoPlayer(ppn), BassPlayer(bassPlayerName), Drummer(dn)) -> ppn
| SaxTrio(SaxPlayer(spn), BassPlayer(bpn), Drummer(dn)) -> spn
| Quartet(SaxPlayer(spn), PianoPlayer(name), BassPlayer(bpn), Drummer(dn)) -> spn
33. Automatic currying
type Message = {
Id : Guid
Payload : string
}
let addPrefix (prefix:string, message:Message) =
{ Id = message.Id
Payload = prefix + message.Payload }
Evaluate in FSI..
val addPrefix : prefix:string * message:Message -> Message
34. Automatic currying
Evaluate in FSI…
let message = { Id = Guid.NewGuid(); Payload = "my payload" }
let addPrefix' prefix =
fun message ->
{ message with
Payload = prefix + message.Payload }
addPrefix’ “hello "
val it : (Message -> Message) = <fun:it@3>
let addHelloPrefix = addPrefix’ “hello "
addHelloPrefix message
Evaluate in FSI…
val it : Message = {Id = ac4ddf34-4d9d-4c16-869a-500578f33038;
Payload = “hello my payload ";}
35. Automatic currying
Evaluate in FSI…
val it : Message = {Id = ac4ddf34-4d9d-4c16-869a-500578f33038;
Payload = "my prefix to my payload ";}
let addHelloPrefix = addPrefix’’ “hello "
addHelloPrefix message
Evaluate in FSI…
val it : Message = {Id = ac4ddf34-4d9d-4c16-869a-500578f33038;
Payload = “hello my payload ";}
let addPrefix'' prefix message =
{ message with
Payload = prefix + message.Payload }
addPrefix'' "my prefix to " message
36. Functional pipelines
Evaluate in FSI…
val it : Message = {Id = ac4ddf34-4d9d-4c16-869a-500578f33038;
Payload = “header:my payload:footer ";}
let addPostfix postfix message =
{ message with
Payload = message.Payload + postfix }
//hard to read with nested parens
let transformMessage message =
(addPostfix "footer" (addPrefix'' "header:" message))
let transformMessage' message =
message
|> addPrefix'' "header:"
|> addPostfix ":footer"
transformMessage' message
37. Functional pipelines
Evaluate in FSI…
val it : Message = {Id = ac4ddf34-4d9d-4c16-869a-500578f33038;
Payload = “header:my payload:footer ";}
let addPostfix postfix message =
{ message with
Payload = message.Payload + postfix }
//hard to read with nested parens
let transformMessage message =
(addPostfix "footer" (addPrefix'' "header:" message))
let transformMessage' message =
message
|> addPrefix'' "header:"
|> addPostfix ":footer"
transformMessage' message
38. Loops to high level transformations
h u z z a h
u z z a
40. Imperative C# implementation
static bool IsPalindrome(string candidate)
{
var endIndex = candidate.Length - 1;
for(var i = 0; i < candidate.Length/2; i++)
{
if (candidate[i] != candidate[endIndex - i])
return false;
}
return true;
}
41. Imperative loop in F#
let palindromeWithWhileLoop (candidate:string) =
let endIndex = candidate.Length - 1
let mutable isPalindrome = true
let mutable i = 0
while i < candidate.Length/2 && isPalindrome do
if candidate.[i] <> candidate.[endIndex - i] then
isPalindrome <- false
i <- i + 1
isPalindrome
42. Lets remove the mutable variables
let palindromeWithRecursiveLoop (candidate:string) =
let endIndex = candidate.Length - 1
let rec loop i isPalindrome =
if i < candidate.Length/2 && isPalindrome then
loop (i + 1) (candidate.[i] = candidate.[endIndex - i])
else
isPalindrome
loop 0 true
43. Simplify with pattern matching
let rec isPalindrome candidate =
let exceptLast list = (list |> List.truncate (list.Length - 1))
match candidate with
| [] -> true
| [x] -> true
| [x1; x2] -> x1 = x2
| x1 :: xs -> x1 = List.last xs && xs |> exceptLast |> isPalindrome
44. Removing the imperative loop
let palindromeWithTryFind (candidate: string) =
candidate
|> Seq.mapi (fun index c -> (index, c))
|> Seq.tryFind (fun (index, c) ->
let indexFromEnd = (Seq.length candidate) - (index + 1)
indexFromEnd >= index && c <> candidate.[indexFromEnd])
|> Option.isNone
45. Transform only the palindromes
let toUppercasePalindromes candidates =
candidates
|> Seq.filter (fun candidate -> isPalindrome candidate)
|> Seq.map (fun palindrome -> palindrome.ToUpper())
Simplified…
let toUppercasePalindromes candidates =
candidates
|> Seq.filter isPalindrome
|> Seq.map (fun palindrome -> palindrome.ToUpper())
[ "anina"; "aninaa"; "aniina"; ""; "a"; "at"; "anireina" ]
|> toUppercasePalindromes |> Seq.toList
Evaluate in FSI…
val it : string list = ["ANINA"; "ANIINA"; ""; "A"]