FSharpPlus


Introduktion till FSharpPlus

  • Ladda ner binärer från Nuget.

  • Öppna en F# skriptfil eller interaktiv F# prompt, referera till biblioteket och öppna namnrymd
1: 
2: 
#r @"../../../src/FSharpPlus/bin/Release/net45/FSharpPlus.dll"
open FSharpPlus

Ignorera varningar om F# metadata om några.

Vi kan nu starta med en snabb tur av de features som finns i F#+.

Generiska funktioner

De finns automatiskt tillgängliga när du har öppnat FSharpPlus namnrymd

här är ett exempel med map (fmap för Haskelliter, Select för Csharpare):

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
map string [|2;3;4;5|]
// val it : string [] = [|"2"; "3"; "4"; "5"|]

map ((+) 9) (Some 3)
// val it : int option = Some 12

open FSharpPlus.Data

map string (NonEmptyList.create 2 [3;4;5])
// val it : NonEmptyList<string> = {Head = "2"; Tail = ["3"; "4"; "5"];}

Dessa funktioer finns också tillgängliga för dina egna typer så länge de innehåller metoder med förväntade signaturer

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type Tree<'t> =
    | Tree of 't * Tree<'t> * Tree<'t>
    | Leaf of 't
    static member Map (x:Tree<'a>, f) = 
        let rec loop f = function
            | Leaf x -> Leaf (f x)
            | Tree (x, t1, t2) -> Tree (f x, loop f t1, loop f t2)
        loop f x

map ((*) 10) (Tree(6, Tree(2, Leaf 1, Leaf 3), Leaf 9))
// val it : Tree<int> = Tree (60,Tree (20,Leaf 10,Leaf 30),Leaf 90)

Generiska funktioner ser kanske exotiska ut med F#-ögon, som enbart sparar några få tangenttryckningar (map istället för List.map eller Array.map). Det de däremot gör är att tillåta dig nå en högre abstraktionsnivå genom att använda ad-hoc polymorphism.

Ännu mer intressant är användningen av operatorer. Du kan inte prefixa dem med den modul de tillhör och förvänta dig att samma operator den fungerar för flera typer i samma utrymme. Ett exempel är att många F#-bibliotek definierar bind operator (>>=) men att dessa inte går att använda samtidigt utan att behöva lägga till ett prefix t.ex. State.(>>=) och Reader.(>>=) vilket gör att nyttan av operatorn försvinner.

Här har du en färdig att använda generisk bind operator: >>=

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let x = ["hello";" ";"world"] >>= (fun x -> Seq.toList x)
// val x : char list = ['h'; 'e'; 'l'; 'l'; 'o'; ' '; 'w'; 'o'; 'r'; 'l'; 'd']


let tryParseInt : string -> int option = tryParse
let tryDivide x n = if n = 0 then None else Some (x / n)

let y = Some "20" >>= tryParseInt >>= tryDivide 100
// val y : int option = Some 5

Du har också Kleislikompositionen familiärt kallad fiskoperatorn: >=>

Denna operator är populär inom F# tack vare Railway Oriented Programming.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let parseAndDivide100By = tryParseInt >=> tryDivide 100

let parsedAndDivide100By20 = parseAndDivide100By "20"   // Some 5
let parsedAndDivide100By0' = parseAndDivide100By "zero" // None
let parsedAndDivide100By0  = parseAndDivide100By "0"    // None

let parseElement n = List.tryItem n >=> tryParseInt
let parsedElement  = parseElement 2 ["0"; "1";"2"]

Glöm inte bort att ovan använda operatorer är generiska, varför vi kan ändra på typen av våra funktioner och få annorlunda funktionalitet gratis:

Testkoden är oförändrad, men vi får mer intressant funktionalitet

1: 
2: 
3: 
4: 
5: 
let parseAndDivide100By = tryParseInt >=> tryDivide 100

let parsedAndDivide100By20 = parseAndDivide100By "20"   // Choice1Of2 5
let parsedAndDivide100By0' = parseAndDivide100By "zero" // Choice2Of2 "Failed to parse zero"
let parsedAndDivide100By0  = parseAndDivide100By "0"    // Choice2Of2 "Can't divide by zero"

När vi arbetar med kombinatorer är den generiska applikativa functoroperatorn (space invaders) väldigt praktisk: <*>

1: 
2: 
3: 
let sumAllOptions = Some (+) <*> Some 2 <*> Some 10     // val sumAllOptions : int option = Some 12

let sumAllElemets = [(+)] <*> [10; 100] <*> [1; 2; 3]   // int list = [11; 12; 13; 101; 102; 103]

För mer detaljer och funktionalitet, se generiska operatorer och funktioner

Här är alla generiska operatorer och funktioner

Och här är en kort förklaring av Functor, Applicativ och Monad-abstraktionerna med kodexempel.

Lins

Från https://github.com/ekmett/lens/wiki/Examples

Först, öppna F#+ Lens

1: 
open FSharpPlus.Lens

Nu kan du läsa från linser (_2 är en lins för den andra komponenten av en tupel)

1: 
2: 
let r1 = ("hello","world")^._2
// val it : string = "world"

och du kan skriva till linser.

1: 
2: 
let r2 = setl _2 42 ("hello","world")
// val it : string * int = ("hello", 42)

Att knyta ihop linser för att läsa (eller skriva) går i den ordning som en imperativ programmerare skulle förvänta sig: använder sig av (<<).

1: 
2: 
3: 
4: 
5: 
let r3 = ("hello",("world","!!!"))^.(_2 << _1)
// val it : string = "world"

let r4 = setl (_2 << _1) 42 ("hello",("world","!!!"))
// val it : string * (int * string) = ("hello", (42, "!!!"))

Du kan också göra en läsfunktion av en ren funktion genom att använda to'.

1: 
2: 
let r5 = "hello"^.to' length
// val it : int = 5

Du kan enkelt komponera läsfunktioner med linser genom att använda (<<).

1: 
2: 
let r6 = ("hello",("world","!!!"))^. (_2 << _2 << to' length)
// val it : int = 3

Som vi såg ovan, kan du skriva till linser och dessa uppdateringar förändrar vilken typ behållaren har. (.->) är ett infix alias för set.

1: 
2: 
let r7 = _1 .-> "hello" <| ((),"world")
// val it : string * string = ("hello", "world")

Den kan avnändas tillsammans med (|>) för en bekant von Neumann-stil av tilldelningssyntax:

1: 
2: 
let r8 = ((), "world") |> _1 .-> "hello"
// val it : string * string = ("hello", "world")

Omvänt kan view användas som ett prefix alias för (^.).

1: 
2: 
let r9 = view _2 (10,20)
// val it : int = 20

För mer detaljer:

Här är en full genomgång av liner och all annan optik

Ta dig en titt på alla linsfunktioner

namespace FSharpPlus
val map : f:('T -> 'U) -> x:'Functor<'T> -> 'Functor<'U> (requires member Map)
Multiple items
val string : value:'T -> string

--------------------
type string = System.String
union case Option.Some: Value: 'T -> Option<'T>
namespace FSharpPlus.Data
Multiple items
module NonEmptyList

from FSharpPlus.Data

--------------------
type NonEmptyList<'t> =
  { Head: 't
    Tail: 't list }
    interface IReadOnlyList<'t>
    interface IReadOnlyCollection<'t>
    interface IEnumerable
    interface IEnumerable<'t>
    member GetSlice : (int option * int option -> NonEmptyList<'t>)
    member Item : (int -> 't)
    member Length : int
    static member Choice : source:NonEmptyList<'Alt<'T>> -> 'Alt<'T> (requires member IsAltLeftZero and member ( <|> ))
    static member Duplicate : s:NonEmptyList<'a> * _impl:Duplicate -> NonEmptyList<NonEmptyList<'a>>
    static member Extract : NonEmptyList<'t> -> 't
    ...
val create : x:'a -> xs:'a list -> NonEmptyList<'a>
Multiple items
union case Tree.Tree: 't * Tree<'t> * Tree<'t> -> Tree<'t>

--------------------
type Tree<'t> =
  | Tree of 't * Tree<'t> * Tree<'t>
  | Leaf of 't
    static member Map : x:Tree<'a> * f:('a -> 'a0) -> Tree<'a0>
union case Tree.Leaf: 't -> Tree<'t>
Multiple items
module Map

from FSharpPlus

--------------------
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
  ...

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val x : Tree<'a>
val f : ('a -> 'a0)
val loop : (('b -> 'c) -> Tree<'b> -> Tree<'c>)
val f : ('b -> 'c)
val x : 'b
val t1 : Tree<'b>
val t2 : Tree<'b>
val x : char list
val x : string
Multiple items
module Seq

from FSharpPlus.Data

--------------------
module Seq

from FSharpPlus.Operators

--------------------
module Seq

from FSharpPlus

--------------------
module Seq

from Microsoft.FSharp.Collections
val toList : source:seq<'T> -> 'T list
val tryParseInt : (string -> int option)
Multiple items
val int : value:'T -> int (requires member op_Explicit)

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

--------------------
type int<'Measure> = int
Multiple items
val option : f:('g -> 'h) -> n:'h -> _arg1:'g option -> 'h

--------------------
type 'T option = Option<'T>
val tryParse : value:string -> 'b option (requires member TryParse)
val tryDivide : x:int -> n:int -> int option
val x : int
val n : int
union case Option.None: Option<'T>
val y : int option
val parseAndDivide100By : (string -> int option)
val parsedAndDivide100By20 : int option
val parsedAndDivide100By0' : int option
val parsedAndDivide100By0 : int option
val parseElement : n:int -> (string list -> int option)
Multiple items
module List

from FSharpPlus.Data

--------------------
module List

from FSharpPlus

--------------------
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
    interface IReadOnlyList<'T>
    interface IReadOnlyCollection<'T>
    interface IEnumerable
    interface IEnumerable<'T>
    member GetSlice : startIndex:int option * endIndex:int option -> 'T list
    member Head : 'T
    member IsEmpty : bool
    member Item : index:int -> 'T with get
    member Length : int
    member Tail : 'T list
    ...
val tryItem : index:int -> list:'T list -> 'T option
val parsedElement : int option
module E2

from Tutorial
val tryParseInt : x:string -> Choice<int,string>
Multiple items
module Choice

from FSharpPlus

--------------------
type Choice<'T1,'T2> =
  | Choice1Of2 of 'T1
  | Choice2Of2 of 'T2

--------------------
type Choice<'T1,'T2,'T3> =
  | Choice1Of3 of 'T1
  | Choice2Of3 of 'T2
  | Choice3Of3 of 'T3

--------------------
type Choice<'T1,'T2,'T3,'T4> =
  | Choice1Of4 of 'T1
  | Choice2Of4 of 'T2
  | Choice3Of4 of 'T3
  | Choice4Of4 of 'T4

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5> =
  | Choice1Of5 of 'T1
  | Choice2Of5 of 'T2
  | Choice3Of5 of 'T3
  | Choice4Of5 of 'T4
  | Choice5Of5 of 'T5

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6> =
  | Choice1Of6 of 'T1
  | Choice2Of6 of 'T2
  | Choice3Of6 of 'T3
  | Choice4Of6 of 'T4
  | Choice5Of6 of 'T5
  | Choice6Of6 of 'T6

--------------------
type Choice<'T1,'T2,'T3,'T4,'T5,'T6,'T7> =
  | Choice1Of7 of 'T1
  | Choice2Of7 of 'T2
  | Choice3Of7 of 'T3
  | Choice4Of7 of 'T4
  | Choice5Of7 of 'T5
  | Choice6Of7 of 'T6
  | Choice7Of7 of 'T7
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
val tryDivide : x:int -> n:int -> Choice<int,string>
val parseAndDivide100By : (string -> Choice<int,string>)
val parsedAndDivide100By20 : Choice<int,string>
val parsedAndDivide100By0' : Choice<int,string>
val parsedAndDivide100By0 : Choice<int,string>
val sumAllOptions : int option
val sumAllElemets : int list
module Lens

from FSharpPlus
val r1 : string
val _2 : f:('a -> 'b) -> t:'f -> 'e (requires member Map and member MapItem2 and member get_Item2)
val r2 : string * int
val setl : optic:(('a -> Identity<'b>) -> 's -> Identity<'t>) -> value:'b -> source:'s -> 't
val r3 : string
val _1 : f:('a -> 'b) -> t:'f -> 'e (requires member Map and member MapItem1 and member get_Item1)
val r4 : string * (int * string)
val r5 : int
val to' : k:('a -> 'b) -> ('c -> 'd) (requires member Dimap and member Contramap)
val length : source:'Foldable<'T> -> int (requires member Length)
val r6 : int
val r7 : string * string
val r8 : string * string
val r9 : int
val view : optic:(('a -> Const<'a,'b>) -> 's -> Const<'a,'t>) -> source:'s -> 'a
Fork me on GitHub