FSharpPlus


Introducing FSharpPlus

#r @"nuget: FSharpPlus"

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):

  open FSharpPlus

  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

Multiple items
type AutoOpenAttribute = inherit Attribute new: unit -> AutoOpenAttribute + 1 overload member Path: string

--------------------
new: unit -> AutoOpenAttribute
new: path: string -> AutoOpenAttribute
module E1 from Tutorial
namespace FSharpPlus
val map: f: ('T -> 'U) -> x: 'Functor<'T> -> 'Functor<'U> (requires member Map)
<summary>Lifts a function into a Functor.</summary>
<category index="1">Functor</category>
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
<summary> Basic operations on NonEmptyList </summary>

--------------------
type NonEmptyList<'t> = { Head: 't Tail: 't list } interface NonEmptySeq<'t> interface IReadOnlyList<'t> interface IReadOnlyCollection<'t> interface IEnumerable interface IEnumerable<'t> static member (+) : NonEmptyList<'a1> * x: NonEmptyList<'a1> -> NonEmptyList<'a1> static member (<*>) : f: NonEmptyList<('T -> 'U)> * x: NonEmptyList<'T> -> NonEmptyList<'U> static member (<.>) : f: NonEmptyList<('T -> 'U)> * x: NonEmptyList<'T> -> NonEmptyList<'U> static member (=>>) : s: NonEmptyList<'a1> * g: (NonEmptyList<'a1> -> 'b) -> NonEmptyList<'b> static member (>>=) : NonEmptyList<'a1> * f: ('a1 -> NonEmptyList<'b>) -> NonEmptyList<'b> ...
<summary> A type-safe list that contains at least one element. </summary>
val create: x: 'a -> xs: 'a list -> NonEmptyList<'a>
<summary>Builds a non empty list.</summary>
't
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
<summary> Additional operations on Map&lt;'Key, 'Value&gt; </summary>

--------------------
module Map from Microsoft.FSharp.Collections

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

--------------------
new: elements: ('Key * 'Value) seq -> Map<'Key,'Value>
val x: Tree<'a>
'a
val f: ('a -> 'a0)
val loop: f: ('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
<summary> Additional operations on Seq </summary>

--------------------
module Seq from FSharpPlus.Operators

--------------------
module Seq from FSharpPlus
<summary> Additional operations on Seq </summary>

--------------------
module Seq from Microsoft.FSharp.Collections
val toList: source: 'T seq -> '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
<summary> Takes a function, a default value and a option value. If the option value is None, the function returns the default value. Otherwise, it applies the function to the value inside Some and returns the result. </summary>
<category index="0">Common Combinators</category>


--------------------
type 'T option = Option<'T>
val tryParse: value: string -> 'T option (requires member TryParse)
<summary> Converts to a value from its string representation. Returns None if the convertion doesn't succeed. </summary>
<category index="21">Converter</category>
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
<summary> Additional operations on List </summary>

--------------------
module List from FSharpPlus
<summary> Additional operations on List </summary>

--------------------
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
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
<summary> Additional operations on Choice </summary>

--------------------
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
<summary> Lens functions and operators </summary>
val r1: string
val _2: f: ('a -> 'b) -> t: 'f -> 'e (requires member Map and member MapItem2 and member Item2)
<summary> Lens for the second element of a tuple </summary>
val r2: string * int
val setl: lens: (('a -> Data.Identity<'b>) -> 'c -> Data.Identity<'d>) -> v: 'b -> ('c -> 'd)
<summary>Write to a lens.</summary>
<param name="lens">The lens.</param>
<param name="v">The value we want to write in the part targeted by the lens.</param>
<param name="source">The original object.</param>
<returns>The new object with the value modified.</returns>
val r3: string
val _1: f: ('a -> 'b) -> t: 'f -> 'e (requires member Map and member MapItem1 and member Item1)
<summary> Lens for the first element of a tuple </summary>
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)
<summary>Gets the number of elements in the foldable.</summary>
<category index="11">Foldable</category>
<param name="source">The input foldable.</param>
<returns>The length of the foldable.</returns>
val r6: int
val r7: string * string
val r8: string * string
val r9: int
val view: lens: (('a -> Data.Const<'a,'b>) -> 'c -> Data.Const<'d,'e>) -> ('c -> 'd)
<summary>Read from a lens.</summary>
<param name="lens">The lens.</param>
<param name="source">The object.</param>
<returns>The part the lens is targeting.</returns>