open FSharpPlus
Ignore warnings about F# metadata if any.
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):
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
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
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>
<example id="string-example"><code lang="fsharp"></code></example>
--------------------
type string = System.String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
union case Option.Some: Value: 'T -> Option<'T>
<summary>The representation of "Value of type 'T"</summary>
<param name="Value">The input value.</param>
<returns>An option representing the value.</returns>
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 (=>>) : s: NonEmptyList<'a1> * g: (NonEmptyList<'a1> -> 'b) -> NonEmptyList<'b>
static member (>>=) : NonEmptyList<'a1> * f: ('a1 -> NonEmptyList<'b>) -> NonEmptyList<'b>
static member Choice: source: NonEmptyList<'Alt<'T>> -> 'Alt<'T> (requires member IsAltLeftZero and member ``<|>``)
...
<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>
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
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.FSharpMap`2" />.</summary>
--------------------
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 Change: key: 'Key * f: ('Value option -> 'Value option) -> Map<'Key,'Value>
...
<summary>Immutable maps based on binary trees, where keys are ordered by F# generic comparison. By default
comparison is the F# structural comparison function or uses implementations of the IComparable interface on key values.</summary>
<remarks>See the <see cref="T:Microsoft.FSharp.Collections.MapModule" /> module for further operations on maps.
All members of this class are thread-safe and may be used concurrently from multiple threads.</remarks>
--------------------
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
<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
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.seq`1" />.</summary>
val toList: source: seq<'T> -> 'T list
<summary>Builds a list from the given collection.</summary>
<param name="source">The input sequence.</param>
<returns>The result list.</returns>
<exception cref="T:System.ArgumentNullException">Thrown when the input sequence is null.</exception>
<example id="tolist-1"><code lang="fsharp">
let inputs = seq { 1; 2; 5 }
inputs |> Seq.toList
</code>
Evaluates to <c>[ 1; 2; 5 ]</c>.
</example>
val tryParseInt: (string -> int option)
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
<summary>Converts the argument to signed 32-bit integer. This is a direct conversion for all
primitive numeric types. For strings, the input is converted using <c>Int32.Parse()</c>
with InvariantCulture settings. Otherwise the operation requires an appropriate
static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted int</returns>
<example id="int-example"><code lang="fsharp"></code></example>
--------------------
[<Struct>]
type int = int32
<summary>An abbreviation for the CLI type <see cref="T:System.Int32" />.</summary>
<category>Basic Types</category>
--------------------
type int<'Measure> =
int
<summary>The type of 32-bit signed integer numbers, annotated with a unit of measure. The unit
of measure is erased in compiled code and when values of this type
are analyzed using reflection. The type is representationally equivalent to
<see cref="T:System.Int32" />.</summary>
<category>Basic Types with Units of Measure</category>
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>
<summary>The type of optional values. When used from other CLI languages the
empty option is the <c>null</c> value. </summary>
<remarks>Use the constructors <c>Some</c> and <c>None</c> to create values of this type.
Use the values in the <c>Option</c> module to manipulate values of this type,
or pattern match against the values directly.
'None' values will appear as the value <c>null</c> to other CLI languages.
Instance methods on this type will appear as static methods to other CLI languages
due to the use of <c>null</c> as a value representation.</remarks>
<category index="3">Options</category>
val tryParse: value: string -> 'a 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>
<summary>The representation of "No value"</summary>
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
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.list`1" />.</summary>
<namespacedoc><summary>Operations for collections such as lists, arrays, sets, maps and sequences. See also
<a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/fsharp-collection-types">F# Collection Types</a> in the F# Language Guide.
</summary></namespacedoc>
--------------------
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
...
<summary>The type of immutable singly-linked lists.</summary>
<remarks>Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or
the notation <c>[1;2;3]</c>. Use the values in the <c>List</c> module to manipulate
values of this type, or pattern match against the values directly.
</remarks>
<exclude />
val tryItem: index: int -> list: 'T list -> 'T option
<summary>Tries to find the nth element in the list.
Returns <c>None</c> if index is negative or the list does not contain enough elements.</summary>
<param name="index">The index to retrieve.</param>
<param name="list">The input list.</param>
<returns>The value at the given index or <c>None</c>.</returns>
<example id="tryitem-1"><code lang="fsharp">
let inputs = ["a"; "b"; "c"]
inputs |> List.tryItem 1
</code>
Evaluates to <c>Some "b"</c>.
</example>
<example id="tryitem-2"><code lang="fsharp">
let inputs = ["a"; "b"; "c"]
inputs |> List.tryItem 4
</code>
Evaluates to <c>None</c>.
</example>
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
<summary>Helper types for active patterns with 2 choices.</summary>
<category index="5">Choices and Results</category>
--------------------
type Choice<'T1,'T2,'T3> =
| Choice1Of3 of 'T1
| Choice2Of3 of 'T2
| Choice3Of3 of 'T3
<summary>Helper types for active patterns with 3 choices.</summary>
<category>Choices and Results</category>
--------------------
type Choice<'T1,'T2,'T3,'T4> =
| Choice1Of4 of 'T1
| Choice2Of4 of 'T2
| Choice3Of4 of 'T3
| Choice4Of4 of 'T4
<summary>Helper types for active patterns with 4 choices.</summary>
<category>Choices and Results</category>
--------------------
type Choice<'T1,'T2,'T3,'T4,'T5> =
| Choice1Of5 of 'T1
| Choice2Of5 of 'T2
| Choice3Of5 of 'T3
| Choice4Of5 of 'T4
| Choice5Of5 of 'T5
<summary>Helper types for active patterns with 5 choices.</summary>
<category>Choices and Results</category>
--------------------
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
<summary>Helper types for active patterns with 6 choices.</summary>
<category>Choices and Results</category>
--------------------
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
<summary>Helper types for active patterns with 7 choices.</summary>
<category>Choices and Results</category>
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
<summary>Choice 1 of 2 choices</summary>
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
<summary>Choice 2 of 2 choices</summary>
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 get_Item2)
<summary>
Lens for the second element of a tuple
</summary>
val r2: string * int
val setl: optic: (('a -> Identity<'b>) -> 's -> Identity<'t>) -> value: 'b -> source: 's -> 't
<summary>Write to a lens.</summary>
<param name="optic">The lens.</param>
<param name="value">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 get_Item1)
<summary>
Lens for the first element of a tuple
</summary>
val r4: string * (int * string)
val r5: int
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: optic: (('a -> Const<'a,'b>) -> 's -> Const<'a,'t>) -> source: 's -> 'a
<summary>Read from a lens.</summary>
<param name="optic">The lens.</param>
<param name="source">The object.</param>
<returns>The part the lens is targeting.</returns>