Advanced Functional Programming in F#

Deep dive into map

let rec map f list =
  match list with
  | [] -> []
  | x::xs -> (f x) :: (map f xs)

Can we map into other things?

Category Theory

Container Type

Type Container<'a> = Container of 'a

let map f xCont =
  match xCont with
  | Container x -> Container (f x)


> map (fun x -> x + 1) (Container 1)
// val it : int Container = Container 2

Functors

  • Generic data type
  • Map function
  • functor laws

Map

  • Names: map, fmap, lift, select
  • Operators: <$>, <!>, $
  • Signature: (a -> b) -> E<a> -> E<b>

Identity

  • Names: identity, id

  • Operators : None

  • Signature: a -> a

Option

type Option <'a> =
    | Some of 'a
    | None

let map f xOpt =
  match xOpt with
  | Some x -> Some (f x)
  | None -> None

let (<!>) = map

let id x = x

//Example
let add1 x = x + 1

let someone = Some 1
>add1 <!> someone
//val it : int option = Some 2

>id someone
//val it : int option = Some 1

List

let rec map f list =
  match list with
  | [] -> []
  | x::xs -> (f x) :: (map f xs)

let (<!>) = map

let id x = x

//Example
let add1 = x + 1

let myList = [1; 2; 3]

>add1 <!> myList
//val it : int list = [2; 3; 4]

>id myList
//val it : int list = [1; 2; 3]

Functor Laws (in Haskell)

Identity:     fmap id       =  id
Composition:  fmap (g . f)  =  fmap g . fmap f

Applicative Functors

  • Generic Data Type
  • Apply and Pure functions
  • Applicative Functor Laws

Pure

  • Names: return, pure, unit, yield, point
  • Operators: none
  • Signature: a -> E<a>

Apply

  • Names: apply, ap
  • Operators: <*>
  • Signature: E<(a->b)> -> E<a> -> E<b>

Option

let pure x = Some x

let apply fOpt xOpt =
  match fOpt, xOpt with
  | Some f, Some x -> Some (f x)
  | _ -> None

let (<*>) = apply

//Example
let add x y = x + y

> (Some (add 1)) <*> (Some 2)
//val it : int option = Some 3

> add <!> (Some 1) <*> (Some 2)
//val it : int option = Some 3

> pure add <*> (Some 1) <*> (Some 2)
//val it : int option = Some 3

List

let pure x = [x]

let apply fList xList =
  [ for f in fList do
    for x in xList do
      yield f x ]

let (<*>) = apply

//Example
let add x y = x + y

>[(add 1); (add 2)] <*> [3; 4]
//val it : int list = [4; 5; 5; 6]

>add <!> [1; 2] <*> [3; 4]
//val it : int list = [4; 5; 5; 6]

>pure add <*> [1; 2] <*> [3; 4]
//val it : int list = [4; 5; 5; 6]

Applicative Functor Laws

Identity:     pure id <*> v               =  v
Homomorphism: pure f <*> pure x           =  pure (f x)
Interchange:  u <*> pure y                =  pure ($ y) <*> u
Composition:  pure (.) <*> u <*> v <*> w  =  u <*> (v <*> w)

Lift2

  • Names: lift2, lift3, .. liftN
  • Operators: None
  • Signature for Lift2: (a -> b -> c) -> E<a> -> E<b> -> E<c>

LiftN

let lift2 f x y =
  f <!> x <*> y

let lift3 f x y z =
  f <!> x <*> y <*> z

let lift4 f x y z w =
  f <!> x <*> y <*> z <*> w

let lift5 f x y z w v =
  f <!> x <*> y <*> z <*> w <*> v

let lift6 f x y z w v u =
  f <!> x <*> y <*> z <*> w <*> v <*> u

let lift7 f x y z w v u t =
  f <!> x <*> y <*> z <*> w <*> v <*> u <*> t

>lift2 add (Some 1) (Some 2)
//val it : int option = Some 3

Redefining map

let map f x = 
    (pure f) <*> x

let map' f x =
    x |> apply (pure f)

Redefining Apply

let lift2' f x y =
    match x, y with
    | Some z, Some w -> Some (f z w)
    | _ -> None

let apply' fOpt xOpt =
    lift2 (fun g x -> g x) fOpt xOpt

Combiners

let (<*) x y = 
    lift2 (fun left right -> left) x y 

let (*>) x y = 
    lift2 (fun left right -> right) x y 


> [1;2] <* [3;4;5]   
//val it : int list = [1; 1; 1; 2; 2; 2]

> [1;2] *> [3;4;5]   
//val it : int list = [3; 4; 5; 3; 4; 5]

ZipList

let rec zipList fList xList  = 
    match fList,xList with
    | [],_ | _,[] -> []  
    | (f::fs),(x::xs) -> 
        (f x) :: (zipList fs xs)

let add10 x = x + 10
let add20 x = x + 20
let add30 x = x + 30

let (<*>) = zipList 

>[add10; add20; add30] <*> [1; 2; 3] 
//val it : int list = [11; 22; 33]

Monads

  • Generic Data Types
  • Bind and Return functions
  • Monad Laws

Return

  • Names: return, pure, unit, yield, point
  • Operators: none
  • Signature: a -> E<a>

Bind

  • Names: bind, flatMap, andThen, collect, SelectMany
  • Operators: >>=, =<<
  • Signature: (a -> E<b>) -> E<a> -> E<b>

Option

let bind f xOpt =
  match xOpt with
  | Some x -> f x
  | None -> None

let (>>=) x f = bind f x
let (=<<) = bind

let retn x = Some x

//Example
let isApple phone = 
    if phone = "apple" then Some phone
    else None

> (Some "apple") >>= isApple
//val it : string option = Some "apple"

> (Some "android") >>= isApple
//val it : string option = None

> None >>= isApple
//val it : string option = None

List

let bind f list =
  [ for x in list do
    for y in f x do
      yield y ]

let (>>=) x f = bind f x
let (=<<) = bind

let retn x = [x]

//Example
let add1 x = [x + 1]

>[1; 2; 3] >>= add1
//val it : int list = [2; 3; 4]

let evens x = if x % 2 = 0 then [x] else []

[1; 2; 3] >>= evens
//val it : int list = [2]

[] >>= evens
//val it : int list = []

Monad Laws

right unit:     m >>= return     =  m
left unit:      return x >>= f   =  f x
associativity:  (m >>= f) >>= g  =  m >>= (\x -> f x >>= g)

Redefining Map

let map f =
    bind (f >> retn)

Redefining Apply

let apply f x =
    f |> bind (fun g -> map g x)

Kliesli Composition

let (>=>) f g x =
      match f x with
      | Some s -> g s
      | None -> None


//Example
let validate = 
    checkName 
    >> bind checkAge
    >> bind checkHeight

let validate' =
    checkName
    >=> checkAge
    >=> checkHeight

Alternate Bind

//val map : f:('a -> 'b) -> x:'a option -> 'b option
let map f x = 
  match x with
  | Some x -> Some (f x)
  | None -> None

//val join : x:'a option option -> 'a option
let join x =
  match x with
  | Some x -> x
  | None -> None

// val bind : f:('a -> 'b option) -> x:'a -> 'b option
let bind f x =
  join (map f x)

Example

//Example
let answer = Some "42"

let (|Int|_|) str =
  match System.Int32.TryParse(str) with
  | (true,int) -> Some(int)
  | _ -> None

let whatIsTheMeaningOfLife x =
  match x with
  | Int i -> Some i
  | None -> None

>map whatIsTheMeaningOfLife answer
//val it : int option option = Some (Some 42)

>join (map whatIsTheMeaningOfLife answer)
//val it : int option = Some 42

>bind whatIsTheMeaningOfLife answer
//val it : int option = Some 42

Map, Apply & Bind

Signatures

id:       a -> a
pure:     a          -> E<a>

map:     (a -> b)    -> E<a> -> E<b>
apply: E<(a -> b)>   -> E<a> -> E<b>
bind:    (a -> E<b>) -> E<a> -> E<b>

Scale of Power

let replicate n x =
  [1..n] *> [x]

> ((*)2) <!> [2;5;6]
val it : int list = [4; 10; 12] //Length or context does not change

> [((*)2);((*)3)] <*> [2;5;6]
val it : int list = [4,10,12,6,15,18]
//Context does not change, length is the product of the two lists

> [1; 2; 5] >>= (fun x -> replicate x x);;
val it : int list = [1; 2; 2; 5; 5; 5; 5; 5]

> [0; 0; 0] >>= (fun x -> replicate x x);;
val it : int list = []
 //Context does not change in F# (in Haskell it can), length is dynamic

Applicative vs Monadic Style

Independent Data: Applicative

Dependent Data: Monadic

> lift3 diplayKingdoms getBacteria getArchaea getEukaryota

\\ val it : string list = 
 [ "Bacteria";"Archaea";"Excavata";"Amoebozoa";
   "Opisthokonta";"Rhizaria";"Chromalveolata";"Archaeplastida" ]
> let prog data = data |> validateData >> bind saveToDb

> prog goodData
\\val it : Result<'a> = Success goodData

> prog badData
\\val it : Result<'a> = Failure ["Failed to validate."]

Traversables

  • Generic data type
  • Traverse and Sequence functions
  • Traversable Laws

Traverse

  • Names: mapM, traverse, for
  • Operators: none
  • Signature: (a -> E<b>) -> a list -> E<b list>

Sequence

  • Names: sequence
  • Operators: none
  • Signature: E<a> list -> E <a list>

Traverse Option

let cons x xs = x::xs

let rec traverseOptionA f list =
  match list with
  | [] ->  pure []
  | x::xs -> pure cons <*> (f x) <*> (traverseOptionA f xs)

let rec traverseOptionM f list =
  match list with
  | [] -> retn []
  | x::xs ->
    f x >>= (fun y ->
    traverseOptionM f xs >>= (fun z ->
    retn (cons y z) ))

//Example
let add1 x = if x < 3 then Some (x + 1) else None

>traverseOptionA add1 [1; 2; 3]
//val it : int list option = Some [2; 3; 4]

>traverseOptionA add1 [1; 2; 3; 4]
//val it int list option = None

>traverseOptionM add1 [1; 2; 3]
//val it : int list option = Some [2; 3; 4]

>traverseOptionM add1 [1; 2; 3; 4]
//val it int list option = None

Sequence Option

let sequenceOptionA x = traverseOptionA id x

let sequenceOptionM x = traverseOptionM id x

//Example
>sequenceOptionA [(Some 1); (Some 2); (Some 3)]
//val it : int list option = Some [1; 2; 3]

>sequenceOptionA [(Some 1); (Some 2); None]
//val it : int list option = None

>sequenceOptionM [(Some 1); (Some 2); (Some 3)]
//val it : int list option = Some [1; 2; 3]

>sequenceOptionM [(Some 1); (Some 2); None]
//val it : int list option = None

Redefining Traverse

let traverseOptionA f list =
  let initState = pure []
  let folder x xs =
    pure cons <*> (f x) <*> xs

  List.foldBack folder list initState

let traverseOptionM f list =
  let initState = retn []
  let folder x xs =
    f x >>= (fun y ->
    xs >>= (fun z ->
    retn (cons x xs) ))

  List.foldBack folder list initState

Demo

Resources

Want more?

  • Functional programming
    • Catamorphisms and Foldables
    • Monoids
    • Lenses
  • F# Features
    • Computational expressions
    • Type providers
  • Functional programming in Javascript

Advanced Functional Programming in F#

By cgoboncan_ebsi

Advanced Functional Programming in F#

  • 1,333