val plus : a:'a -> b:'b -> 'c (requires member ( + ))

Full name: index.plus
val a : 'a (requires member ( + ))
val b : 'a (requires member ( + ))
val intResult : int

Full name: index.intResult
val decimalResult : decimal

Full name: index.decimalResult
Multiple items
module Option

from Microsoft.FSharp.Core

--------------------
type Option<'a> =
  | Some of 'a
  | None
  static member Map : x:Option<'a> * f:('a -> 'b) -> Option<'b>
  static member Map : x:Option<'a1> * f:('a1 -> 'a2) -> Option<'a2>
  static member Return : x:'a1 -> Option<'a1>
  static member ( <*> ) : f:'a1 * x:'a2 -> 'a3

Full name: index.Option<_>
union case Option.Some: 'a -> Option<'a>
union case Option.None: Option<'a>
val add5 : opt:Option<int> -> Option<int>

Full name: index.add5
val opt : Option<int>
val a : int
val add10 : opt:Option<int> -> Option<int>

Full name: index.add10
val map : f:('a -> 'b) -> opt:Option<'a> -> Option<'b>

Full name: index.Option.map
val f : ('a -> 'b)
val opt : Option<'a>
val a : 'a
val add5 : (Option<int> -> Option<int>)

Full name: index.add5
Multiple items
module Option

from index

--------------------
module Option

from Microsoft.FSharp.Core

--------------------
type Option<'a> =
  | Some of 'a
  | None
  static member Map : x:Option<'a> * f:('a -> 'b) -> Option<'b>
  static member Map : x:Option<'a1> * f:('a1 -> 'a2) -> Option<'a2>
  static member Return : x:'a1 -> Option<'a1>
  static member ( <*> ) : f:'a1 * x:'a2 -> 'a3

Full name: index.Option<_>
Multiple items
val map : f:('a -> 'b) -> opt:Option<'a> -> Option<'b>

Full name: index.Option.map

--------------------
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
Multiple items
module Result

from Microsoft.FSharp.Core

--------------------
type Result<'a,'e> =
  | Ok of 'a
  | Error of 'e

Full name: index.Result<_,_>
union case Result.Ok: 'a -> Result<'a,'e>
union case Result.Error: 'e -> Result<'a,'e>
val add5 : result:Result<int,'a> -> Result<int,'a>

Full name: index.add5
val result : Result<int,'a>
val e : 'a
val add10 : result:Result<int,'a> -> Result<int,'a>

Full name: index.add10
val map : f:('a -> 'b) -> r:Result<'a,'c> -> Result<'b,'c>

Full name: index.Result.map
val r : Result<'a,'b>
val add5 : (Result<int,obj> -> Result<int,obj>)

Full name: index.add5
Multiple items
module Result

from index

--------------------
module Result

from Microsoft.FSharp.Core

--------------------
type Result<'a,'e> =
  | Ok of 'a
  | Error of 'e

Full name: index.Result<_,_>
Multiple items
val map : f:('a -> 'b) -> r:Result<'a,'c> -> Result<'b,'c>

Full name: index.Result.map

--------------------
val map : mapping:('T -> 'U) -> result:Result<'T,'TError> -> Result<'U,'TError>

Full name: Microsoft.FSharp.Core.Result.map
Multiple items
static member Option.Map : x:Option<'a> * f:('a -> 'b) -> Option<'b>

Full name: index.Option`1.Map

--------------------
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IReadOnlyDictionary<'Key,'Value>
  interface IReadOnlyCollection<KeyValuePair<'Key,'Value>>
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val x : Option<'a>
val generalAdd5 : functor:'a -> 'b

Full name: index.generalAdd5
val functor : 'a
module Operators

from Microsoft.FSharp.Core
val generalAdd5WithOp : functor:'a -> '_arg3 (requires member ( <!> ))

Full name: index.generalAdd5WithOp
val functor : 'a (requires member ( <!> ))
Multiple items
union case Option.Some: 'a -> Option<'a>

--------------------
union case Option.Some: Value: 'T -> Option<'T>

--------------------
static member Option.Some : value:'T -> 'T option
Multiple items
union case Result.Ok: 'a -> Result<'a,'e>

--------------------
union case Result.Ok: ResultValue: 'T -> Result<'T,'TError>
type Recipe =
  {Name: obj;
   PersonCount: obj;
   Ingredients: obj;}

Full name: index.Recipe
Recipe.Name: obj
Recipe.PersonCount: obj
Recipe.Ingredients: obj
type RecipeApiParameters =
  {Name: string;
   PersonCount: int;
   Ingredients: obj;}

Full name: index.RecipeApiParameters
RecipeApiParameters.Name: string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
RecipeApiParameters.PersonCount: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
RecipeApiParameters.Ingredients: obj
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
val add : opt1:Option<obj> -> opt2:Option<Option<'a>> -> Option<'a>

Full name: index.add
val opt1 : Option<obj>
val opt2 : Option<Option<'a>>
val a : obj
val b : Option<'a>
val product : opt1:Option<'a> -> opt2:Option<'b> -> Option<'c> (requires member ( * ))

Full name: index.product
val opt1 : Option<'a>
val opt2 : Option<'a> (requires member ( * ))
val b : 'a (requires member ( * ))
val apply : fOpt:Option<('a -> 'b)> -> aOpt:Option<'a> -> Option<'b>

Full name: index.apply
val fOpt : Option<('a -> 'b)>
val aOpt : Option<'a>
Multiple items
static member Option.Map : x:Option<'a1> * f:('a1 -> 'a2) -> Option<'a2>

Full name: index.Option`1.Map

--------------------
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IReadOnlyDictionary<'Key,'Value>
  interface IReadOnlyCollection<KeyValuePair<'Key,'Value>>
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val f : 'a
val x : 'a
static member Option.Return : x:'a1 -> Option<'a1>

Full name: index.Option`1.Return
type Validation<'err,'a> =
  | Failure of 'err
  | Success of 'a

Full name: index.Validation<_,_>
Multiple items
union case Validation.Failure: 'err -> Validation<'err,'a>

--------------------
active recognizer Failure: exn -> string option

Full name: Microsoft.FSharp.Core.Operators.( |Failure|_| )
union case Validation.Success: 'a -> Validation<'err,'a>
val map : f:('T -> 'U) -> _arg1:Validation<'a,'T> -> Validation<'a,'U>

Full name: index.map
val f : ('T -> 'U)
union case Validation.Failure: 'err -> Validation<'err,'a>
val a : 'T
val apply : vf':'a -> va':'b -> Validation<'c,'d> (requires member ( + ))

Full name: index.apply
val vf' : 'a
val va' : 'a
val e1 : 'a (requires member ( + ))
val e2 : 'a (requires member ( + ))
val vf : 'a
val va : 'a
Multiple items
union case Result.Error: 'e -> Result<'a,'e>

--------------------
type Error =
  | NameIsEmpty
  | PesonCountIsNegative
  | IngredientsAreEmpty

Full name: index.Error
union case Error.NameIsEmpty: Error
union case Error.PesonCountIsNegative: Error
union case Error.IngredientsAreEmpty: Error
val parseName : (string -> 'a) (requires member ( <!> ) and member ( <*> ) and member ( <*> ))

Full name: index.parseName
val parsePersonCount : (int -> 'a) (requires member ( <*> ) and member ( <*> ))

Full name: index.parsePersonCount
val parseIngredients : (obj -> 'a) (requires member ( <*> ))

Full name: index.parseIngredients
val parseParameters : params:RecipeApiParameters -> Validation<Recipe,Error list>

Full name: index.parseParameters
val params : RecipeApiParameters
val sqrt : x:'a -> 'b

Full name: index.sqrt
val log : x:'a -> 'b

Full name: index.log
val div : x:'a -> y:'a -> 'a (requires member ( / ))

Full name: index.div
val x : 'a (requires member ( / ))
val y : 'a (requires member ( / ))
val sqrtAndLogAndDiv : x:'a -> y:'b -> 'b (requires member ( / ))

Full name: index.sqrtAndLogAndDiv
val x' : 'a (requires member ( / ))
val sqrt : x:float -> Option<'a>

Full name: index.sqrt
val x : float
val log : x:float -> Option<'a>

Full name: index.log
val div : x:int -> y:int -> Option<int>

Full name: index.div
val x : int
val y : int
val sqrtAndLogAndDiv : x:float -> y:'a -> Option<float>

Full name: index.sqrtAndLogAndDiv
val y : 'a
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val x' : float
val x'' : 'a
val sqrtAndLogAndDiv : x:float -> y:'a -> Option<Option<Option<float>>>

Full name: index.sqrtAndLogAndDiv
val x' : 'a
val join : m:Option<Option<'a>> -> Option<'a>

Full name: index.join
val m : Option<Option<'a>>
val a : Option<'a>
val m : Option<'a>
val f : ('a -> Option<'b>)
val g : ('b -> Option<'c>)
val unsafeSqrt : float

Full name: index.unsafeSqrt
val unsafeLog : float

Full name: index.unsafeLog
val sqrt : x:float -> Option<float>

Full name: index.sqrt
val log : x:float -> Option<float>

Full name: index.log
val unsafeSqrtAndLog : ('a -> 'b)

Full name: index.unsafeSqrtAndLog
val sqrtAndLog : (float -> Option<float>)

Full name: index.sqrtAndLog
val unsafeSqrtAndLogAndDiv : x:float -> y:int -> Option<int>

Full name: index.unsafeSqrtAndLogAndDiv
val x' : Option<float>
val x'' : Option<float>
val result : Option<int>
val sqrtAndLogAndDiv : x:'a -> y:'b -> 'c

Full name: index.sqrtAndLogAndDiv
type SignUpError =
  | InvalidEmail
  | InvalidPassword of obj
  | AccountAlreadyExits

Full name: index.SignUpError
union case SignUpError.InvalidEmail: SignUpError
union case SignUpError.InvalidPassword: obj -> SignUpError
union case SignUpError.AccountAlreadyExits: SignUpError
module String

from Microsoft.FSharp.Core
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IReadOnlyDictionary<'Key,'Value>
  interface IReadOnlyCollection<KeyValuePair<'Key,'Value>>
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool

FSharpPlus - advanced FP concepts in F#

Josef Starýchfojtů

Who am I ?

What is a monad ?

  • Monoid in the category of endofunctors.

How to achieve higher level abstraction in F# ?

1: 
2: 
3: 
let inline plus a b = a + b // (requires member ( + ))
let intResult = plus 5 6 // int
let decimalResult = plus 5m 6m // decimal
  • inline keyword, statically resolved type parameters
  • can make code both faster and slower

Functor

  • Building block for our future
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type Option<'a> =
    | Some of 'a
    | None

let add5 opt =
    match opt with
    | Some a -> Some <| a + 5
    | None -> None

let add10 opt =
    match opt with
    | Some a -> Some <| a + 10
    | None -> None
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
module Option =
    let map f opt =
        match opt with
        | Some a -> Some <| f a
        | None -> None

let add5 = Option.map ((+) 5)
add5 (Some 10) // (Some 15)
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type Result<'a, 'e> =
    | Ok of 'a
    | Error of 'e

let add5 result =
    match result with
    | Ok a -> Ok <| a + 5
    | Error e -> Error e

let add10 result =
    match result with
    | Ok a -> Ok <| a + 10
    | Error e -> Error e
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
module Result =
    let map f r =
        match r with
        | Ok a -> Ok <| f a
        | Error e -> Error e

let add5 = Result.map ((+) 5)
add5 (Ok 10) // (Ok 15)
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type Option<'a> with
    static member Map (x: Option<'a>, f: 'a -> 'b): Option<'b> = Option.map f x

// Works with any type with 'Map' (Functor).
let inline generalAdd5 functor = FsharpPlus.Operators.map ((+) 5) functor
let inline generalAdd5WithOp functor = ((+) 5) <!> functor // Infix map operator

generalAdd5 (Option.Some 5) // (Option.Some 10)
generalAdd5 (Result.Ok 5) // (Result.Ok 10)

Let's get practical !

How to properly and easily do validation ?

Don't, Parse instead !

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type Recipe = { 
    Name: NonEmptyString
    PersonCount: NaturalNumber
    Ingredients: Ingredient NonEmptyList 
}

type RecipeApiParameters = { 
    Name: string
    PersonCount: int
    Ingredients: Ingredient list
}
  • How to parse RecipeApiParameters to Recipe ?
  • And return ALL errors, not just the first one ?

Applicative

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let add opt1 opt2 = 
    match opt1 with
    | Some a ->    
        match opt2 with
        | Some b -> Some a + b
        | None -> None
    | None -> None

let product opt1 opt2 = 
    match opt1 with
    | Some a ->    
        match opt2 with
        | Some b -> Some a * b
        | None -> None
    | None -> None
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let apply (fOpt: Option<'a -> 'b>) (aOpt: Option<'a>): Option<'b> =
    match fOpt with
    | Some f ->    
        match aOpt with
        | Some a -> Some <| f a
        | None -> None
    | None -> None

type Option<'a> with
    static member Map (x, f) = Option.map f x
    static member (<*>) (f, x) = Option.apply f x
    static member Return x = Some x

(result (+)) <*> (Some 5) <*> (Some 5) // Some 10
(+) <!> (Some 5) <*> (Some 5) // Some 10 

Back to our problem

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type Validation<'err, 'a> =
  | Failure of 'err
  | Success of 'a

let map (f: 'T->'U) = function
    | Failure e -> Failure e
    | Success a -> Success (f a) 

let apply vf' va' = 
    match v1', v2' with
    | Failure e1, Failure e2 -> Failure (plus e1 e2)
    | Failure e1, Success _  -> Failure e1
    | Success _ , Failure e2 -> Failure e2
    | Success vf , Success va  -> Success (f a)
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type Error = NameIsEmpty | PesonCountIsNegative | IngredientsAreEmpty

let parseName = 
    NonEmptyString.create >> Validation.ofOption [NameIsEmpty]
let parsePersonCount = 
    NaturalNumber.create >> Validation.ofOption [PesonCountIsNegative]
let parseIngredients = 
    NonEmptyList.create >> Validation.ofOption [IngredientsAreEmpty]

let parseParameters params: Validation<Recipe, Error list> =
	Recipe.create
	<!> parseName params.Name
	<*> parsePersonCount params.PersonCount
	<*> parseIngredients params.Ingredients

parseParameters { Name = "", PersonCount = -1, Ingredients = [] } 
// Failure [NameIsEmpty; PesonCountIsNegative; IngredientsAreEmpty]

Valiation

Now it gets a bit scary (but also practical !)

Monad

1: 
2: 
3: 
4: 
5: 
let sqrt x = Math.Sqrt x
let log x = Math.Log x
let div x y = x / y
let sqrtAndLogAndDiv x y =
    sqrt x |> log |> fun x' -> div x' y
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let sqrt x = if x < 0.0 then None else Some <| Math.Sqrt x
let log x = if x <= 0.0 then None else Some <| Math.Log x
let div x y = if y = 0 then None else Some <| x / y
let sqrtAndLogAndDiv x y: Option<float> =
    match (sqrt x) with
    | Some x' -> 
        match (log x') with
        | Some x'' -> div x'' y
        | None
    | None -> None
1: 
2: 
3: 
4: 
let sqrtAndLogAndDiv x y: Option<Option<Option<float>>> =
    sqrt x
    |> Option.map log
    |> Option.map (Option.map (fun x' -> div x' y))

Monad

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let join (m: Option<Option<'a>>): Option<'a> =
	match m with
	| Some a -> a
	| None -> None

let (>>=) (m: Option<'a>) (f: ('a -> Option<'b>)): Option<'b> =
	map f m |> join

let (>=>) (f: ('a -> Option<'b>)) (g: ('b -> Option<'c>)): ('a -> Option<'c>) = 
    fun a -> f a >>= g

Monad = Extended function composition

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let unsafeSqrt: float = Math.Sqrt
let unsafeLog: float = Math.Log

let sqrt x: Option<float> = if x < 0.0 then None else Some <| Math.Sqrt x
let log x: Option<float> = if x <= 0.0 then None else Some <| Math.Log x

let unsafeSqrtAndLog = unsafeSqrt >> unsafeLog
let sqrtAndLog = sqrt >=> log

Monad computation expression

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let unsafeSqrtAndLogAndDiv x y =
    let x' = sqrt x
    let x'' = log x'
    let result = div x'' y
    result

let sqrtAndLogAndDiv x y = monad {
    let! x' = sqrt x
    let! x'' = log x'
    let! result = div x'' y
    return result
}

Result: Error handling

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type SignUpError = 
    | InvalidEmail
    | InvalidPassword of PasswordRequirements
    | AccountAlreadyExits

createAccount :: Email -> Password -> Result<Account, SignUpError>
verifyAccountNotExists :: Account -> Result<Account, SignUpError>

let signUp email password =
    createAccount email password
    >>= verifyAccountNotExists
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
createAccount :: Email -> Password -> Result<Account, SignUpError>
makeAccountVip :: Account -> Account
verifyAccountNotExists :: Account -> Result<Account, SignUpError>

let signUp email password = monad {
    let! account = createAccount email password
    let vipAccount = makeAccountVip account
    let! result = verifyAccountNotExists vipAccount
    return result
}

Reader: Dependency injection

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let getUser repo email = repo.getByEmail email
let addShoppingList repo shoppingList = repo.add shoppingList

let main userRepo shoppingListRepo email =
    let user = getUser userRepo email
    let shoppingList = ShoppingList.createEmpty user
    addShoppingList shoppingListRepo shoppingList
1: 
2: 
3: 
4: 
5: 
6: 
7: 
type Reader<'r,'t> = Reader of ('r->'t)

module Reader =
    let run (Reader x) = x
    let map  f (Reader m) = Reader (f << m)
    let bind f (Reader m) = Reader (fun r -> run (f (m r)) r)
    let apply (Reader f) (Reader x) = Reader (fun a -> f a ((x: _->'T) a))
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
// Email -> Reader<'e, User> where 'e : IUserRepository 
let getUser<'e when 'e :> IUserRepository> email = 
    Reader(fun (env: 'e) -> env.getByEmail email)

// ShoppingList -> Reader<'e, ShoppingList> where 'e : IShoppingListRepository 
let addShoppingList<'e when 'e :> IShoppingListRepository> shoppingList =
    Reader(fun (env: 'e) -> env.add shoppingList)

// Reader<'e, ShoppingList> where 'e : IUserRepository, IShoppingListRepository 
let main email = monad {
    let! user = getUser email
    let shoppingList = ShoppingList.createEmpty user
    return! addShoppingList shoppingList
}
  • passing repo everywhere is not needed
  • our dependencies compose automatically

Writer: Additional output collecting

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
type Writer<'monoid,'t> = Writer of ('t * 'monoid)

let run (Writer x) = x : 'T * 'Monoid

let map f (Writer (a: 'T, w)) = 
    Writer (f a, w) : Writer<'Monoid,'U>
let inline bind f (Writer (a: 'T, w)) = 
    Writer (let (b, w') = run (f a) in (b, plus w w')) : Writer<'Monoid,'U>
let inline apply  (Writer (f, a)) (Writer (x: 'T, b)) = 
    Writer (f x, plus a b) : Writer<'Monoid,'U>

Writer: Logging

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type Ingredient = { Name: String }
type User = { FavouriteIngredient: Ingredient }

let createIngredient name =
    Writer ({ Name = name }, ["Ingredient created"])
    
let createUser ingredient =
    Writer ({ FavouriteIngredient = ingredient }, ["User created"])
    
let main =
    createIngredient >=> createUser

Writer.run (main "Test") 
// ({FavouriteIngredient = {Name = "Test"}}, [Ingredient created; User created])

Async/Task = Monad

Seq/IEnumerable = Monad

And a lot more ...

Lenses

OOP - mutable write data access, what about us ?

1: 
2: 
3: 
4: 
foreach (var i in shoppingList.Items.Where(i => i.FoodstuffId == someId))
{
    i.Amount = 1.Kg();
}

OOP - mutable write data access, what about us ?

1: 
2: 
3: 
4: 
5: 
6: 
let replaceItem item = 
    if item.FoodstuffId = someId
        then { item with Amount = Kg 1 }
        else item
let newItems = Seq.map replaceItem shoppingList.Items
{ shoppingList with Items = newItems}

Lens - Motivation

1: 
2: 
3: 
let hasFoodstuff i = i.FoodstuffId = someId
let amounts = _shoppingListItems << items << (filtered hasFoodstuff) << _amount
setl amounts (Kg 1) list

Lens = get + set

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
get :: s -> a
set :: s -> a -> s

type Ingredient = { FoodstuffId: Guid; Amount: Kg }

getAmount :: Ingredient -> Kg
let getAmount ingredient = ingredient.Amount

setAmount :: Ingredient -> Kg -> Ingredient
let setAmount ingredient newValue = { ingredient with Amount = newValue } 

Lens = get + set + transform

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
get :: s -> a
set :: s -> a -> s
transfrom :: a -> a
lensWithoutFunctors :: (a -> a) -> s -> s ("get >> transform >> set")
lens :: (a -> F a) -> s -> F s

type Ingredient = { FoodstuffId: Guid; Amount: Kg }

_amount :: (Kg -> F Kg) -> Ingredient -> F Ingredient
let inline _amount f ingredient = 
    map (fun v -> { ingredient with Amount = v }) (f ingredient.Amount)
  • get and set are fine, but there is something more convenient, just one function that can do both
  • note: Lack of abstraction won't show the exact type in F#, F ingredient will be 'b, requires Map'

setl (set)

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type Ingredient = { FoodstuffId: Guid; Amount: Kg }

setAmount :: Kg -> Ingredient -> Ingredient
let setAmount newValue ingredient = setl _amount newValue ingredient

setl :: (('a -> Identity<'a>) -> 's -> Identity<'s>) -> 'a -> 's -> 's
let setl optic value source = 
    Identity.run (optic (fun _ -> Identity value) source

view (get)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
type Ingredient = { FoodstuffId: Guid; Amount: Kg }

getAmount :: Ingredient -> Kg
let getAmount ingredient = view _amount ingredient
let getAmount = view _amount

type Const<'t,'u> = Const of 't with
    static member Map (Const x: Const<_,'T>, _: 'T->'U) = Const x : Const<'C,'U>

view :: (('a -> Const<'a,'a>) -> 's -> Const<'a,'s>) -> 's -> 'a
let view optic source = 
    Const.run (optic Const source)

over (get + set)

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type Ingredient = { FoodstuffId: Guid; Amount: Kg }

increaseAmount :: Ingredient -> Ingredient
let increaseAmount ingredient = over _amount ((+) 1) ingredient

over :: (('a -> Identity<'a>) -> 's -> Identity<'s>) -> ('a -> 'a) -> 's -> 's
let over optic updater source = 
    Identity.run (optic (Identity << updater) source)

Raw usage of the functor

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
type Ingredient = { FoodstuffId: Guid; Amount: Kg }

tryDecrease :: Kg -> Option<Kg>
let tryDecrease kg = 
    if kg = 0
        then None
        else Some <| kg - 1

tryDecreaseAmount :: Ingredient -> Ingredient option
let tryDecreaseAmount ingredient = _amount tryDecrease ingredient

Lens composition

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
lens1 :: (a -> F a) -> (s1 -> F s1)
lens2 :: (s1 -> F s1) -> s2 -> F s2
lens1 >> lens2 :: (a -> F a) -> (s2 -> F s2)

type Ingredient = { FoodstuffId: Guid; Amount: Kg }
type Recipe = { MainIngredient: Ingredient }

let inline _mainIngredient f recipe = 
    map (fun v -> { recipe with MainIngredient = v }) (f recipe.MainIngredient)

let setMainIngredientAmount = setl (_mainIngredient << _amount)

Lens - the true type

1: 
2: 
3: 
4: 
5: 
6: 
lens :: (a -> F b) -> s -> F t

/// Lens for the first element of a tuple
let inline _1 f t = map (fun x -> mapItem1 (fun _ -> x) t) (f (item1 t))

let a = setl _1 "a" (5, "b") // ("a", "b")
  • now we can change type (like element of a tuple)

Use with traverse

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// main use case : (a -> bool) -> (a -> F a) -> 's list -> F ('s list)
_where :: (a -> bool) -> (a -> F a) -> T 's -> F T 's
let inline _where predicate f source =
    let update item = 
        if (predicate item) 
            then f item 
            else result item
    traverse update source
  • Functor is not enough, we need an applicative
  • now we can even point to multiple places (like both tuple values)

Nested data access

1: 
2: 
3: 
let hasFoodstuff i = i.FoodstuffId = someId
let amounts = _shoppingListItems << items << (filtered hasFoodstuff) << _amount
setl amounts (Kg 1) list

Nested data access

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let moveToCity account oldCity newCity =
    let transformAddress a =
        if a.City = oldCity
            then { a with City = newCity }
            else a
            
    let newAddresses = NonEmptyList.map transformAddress account.ContactInfo.Addresses
    { account with ContactInfo = { account.ContactInfo with Addresses = newAddresses } }

Nested data access

1: 
2: 
3: 
let moveToCity account oldCity newCity: Account =
    let addresses = contactInfo << addresses << items
    setl (addresses << (filtered (fun a -> a.City = oldCity)) << city) newCity account

Virtual properties

1: 
2: 
3: 
4: 
5: 
6: 
type Weight = {
    Value: Kg
}

let inline _grams f weight = 
    map (fun v -> { weight with Value = (v / 1000)}) (f <| 1000 * weight.Value)

HTML traversal

1: 
2: 
3: 
4: 
recentPackages :: Response ByteString -> [Maybe Upload]
recentPackages = toList
               $ responseBody . to (decodeUtf8With lenientDecode)
               . html . allNamed (only "tr") . children . to table

Links

I haven't said everything. When you try this, you will have questions.

Hit me with DM on @JStarychfojtu

You can also ask on stack overflow or join FsharpPlus slack channel on FP slack.

Thanks for your attention