FSharpPlus


Introducing FSharpPlus

#r @"../../src/FSharpPlus/bin/Release/net45/FSharpPlus.dll"
open FSharpPlus

Ignore warnings about F# metadata if any.

Now we'll start with a quick overview of the features presented in F#+.

Generic functions

They are automatically available when opening the FSharpPlus namespace

here's an example with map (fmap for Haskellers, Select for C-sharpers):

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"];}

They're also available for your own types as long as they contain the appropriated method with the expected signature

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)

Generic functions may be seen as an exotic thing in F# that only saves a few key strokes (map instead of List.map or Array.map) still they allow you to reach a higher abstraction level, using ad-hoc polymorphism.

But more interesting is the use of operators. You can't prefix them with the module they belong to, well you can but then it's no longer an operator. As an example many F# libraries define the bind operator (>>=) but it's not generic so if you use two different types which are both monads you will need to prefix it e.g. State.(>>=) and Reader.(>>=) which defeats the purpose of having an operator.

Here you have a ready-to-use generic bind operator: >>=

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

You have also the Kleisli composition (fish) operator: >=>

Which is becoming popular in F# after the Railway Oriented Programming tutorial series

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"]

But don't forget the above used operators are generic, so we can change the type of our functions and we get a different functionality for free:

The test code remains unchanged, but we get a more interesting functionality

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"

Also when working with combinators, the generic applicative functor (space invaders) operator is very handy: <*>

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]

For more details and features, see generic operators and functions

Here are all generic operators and functions

And here's a short explanation of Functor, Applicative and Monad abstractions with code samples.

Lens

from https://github.com/ekmett/lens/wiki/Examples

First, open F#+ Lens

open FSharpPlus.Lens

Now, you can read from lenses (_2 is a lens for the second component of a tuple)

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

and you can write to lenses.

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

Composing lenses for reading (or writing) goes in the order an imperative programmer would expect, and just uses (<<).

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, "!!!"))

You can make a Getter out of a pure function with to'.

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

You can easily compose a Getter with a Lens just using (<<). No explicit coercion is necessary.

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

As we saw above, you can write to lenses and these writes can change the type of the container. (.->) is an infix alias for set.

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

It can be used in conjunction with (|>) for familiar von Neumann style assignment syntax:

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

Conversely view, can be used as an prefix alias for (^.).

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

For more details:

Here's a full tour of lens and all other optics

Have a look at all lens functions

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 NonEmptySeq<'t>
    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>>
    ...
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