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ů
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)
|
How to properly and easily do validation ?
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 ?
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
|
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]
|
Now it gets a bit scary (but also practical !)
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
}
|
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])
|
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
|
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