Now we'll start with a quick overview of the features presented in F#+.
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.
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<'Key, 'Value>
</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>