August 7, 2017
Tom Prior
www.prigrammer.com
@priortd
F# Delight
Our Sponsors
Getting started – F# interactive FSI
https://www.microsoft.com/net/download/core
dotnet new console -lang f# -o MyConsoleApp
cd MyConsoleApp
dotnet restore
code .
Create fsx file and start
executing code with f#
interactive!
Just highlight and press
Alt-Enter
The Delights
• Simplicity
• Immutability
• Pattern matching
• Encoding logic in types
• Automatic currying – all functions are one arg functions
• High level transformations and functional pipelines
Immutability – let bindings
let x = 2
Evaluated in FSI…
val x : int = 2
Will this increment x ??
Evaluated in FSI…
val x : int = 2
val it : bool = false
let x = 2
x = x + 1
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
Tuples
let p = ("Tom", 38)
fst p
Evaluated in FSI ….
val p : string * int = ("Tom", 38)
val it : string = "Tom"
Immutability – OO Classes
type Person(name:string) =
member p.Name = name
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;}
Evaluated in FSI…
val it : string = "Tom"
//won't compile as records are immutable.
p.Name <- "Jack"
p.Name
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
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"
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
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
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
Pattern matching on lists
let l = [ 1; 2; 3 ]
1
2
3
[]
//l can be re-written as
let l = 1 :: 2 :: 3 :: []
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
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
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
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")
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"
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
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
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 ";}
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
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
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
Loops to high level transformations
h u z z a h
u z z a
h u z z u h
u z z u
z z
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;
}
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
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
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
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
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"]
Learning Resources
https://fsharpforfunandprofit.com/
Kit Eason’s courses on Pluralsight
Try hackerrank problems in F#
https://www.hackerrank.com/
https://www.pluralsight.com/courses/fsharp-jumpstart

F# delight

  • 1.
    August 7, 2017 TomPrior www.prigrammer.com @priortd F# Delight
  • 2.
  • 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 fileand 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
  • 14.
    Immutability – letbindings let x = 2 Evaluated in FSI… val x : int = 2
  • 15.
    Will this incrementx ?? Evaluated in FSI… val x : int = 2 val it : bool = false let x = 2 x = x + 1
  • 16.
    Specifying Mutability Evaluated inFSI… 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 – OOClasses type Person(name:string) = member p.Name = name
  • 19.
    Immutability – Records typePerson = { 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… valit : string = "Tom" //won't compile as records are immutable. p.Name <- "Jack" p.Name
  • 21.
    Immutable Collections Evaluated inFSI… 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 Evaluatedin 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 inFSI… 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 inFSI… 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 onrecords 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 onlists let l = [ 1; 2; 3 ] 1 2 3 [] //l can be re-written as let l = 1 :: 2 :: 3 :: []
  • 27.
    Evaluated in FSI… vall : 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 statesunrepresentable 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 statesunrepresentable 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 privatedata 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 inFSI… 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 inFSI… 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 inFSI… 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 inFSI… 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 highlevel transformations h u z z a h u z z a
  • 39.
    h u zz u h u z z u z z
  • 40.
    Imperative C# implementation staticbool 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 inF# 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 themutable 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 patternmatching 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 imperativeloop 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 thepalindromes 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"]
  • 46.
    Learning Resources https://fsharpforfunandprofit.com/ Kit Eason’scourses on Pluralsight Try hackerrank problems in F# https://www.hackerrank.com/ https://www.pluralsight.com/courses/fsharp-jumpstart