FSharpPlus


Generic operators and functions

After reviewing extension functions it's natural to want to use generic functions that can work across different types.

F#+ implements generic functions that efficiently call out to specific implementations. This handles existing .Net and F# types, and you can use them on your own and third-party types by implementing expected method names and signatures.

Read about the specific operators:

They're particularly useful in that the specific function called will depend on the input arguments and return type. However, this means you sometimes need to explicitly specify the type if this information is not available (actually it's a good debug technique to temporarily add the types explicitly when the compiler tells you that the types are wrong).

For example:

// Convert the number 42 to bytes... 
// ... here the type is known (42 is an int, the return value is byte[])
let a = 42 |> toBytes;;  
//val a : byte [] = [|42uy; 0uy; 0uy; 0uy|]

// However, this can't compile since the return type is not inferrable
// let b = [|42uy; 0uy; 0uy; 0uy|] |> ofBytes;;  

// The error will be something like:
// 
//  let b = [|42uy; 0uy; 0uy; 0uy|] |> ofBytes;;
//  -----------------------------------^^^^^^^
//
// error FS0071: Type constraint mismatch when applying the default type 'obj'
// for a type inference variable. No overloads match for method 'OfBytes'.
// The available overloads are shown below. Consider adding further type constraints

// [followed by many possible implementations...]

// So, in this case, we have to give the return type:
let b :int = [|42uy; 0uy; 0uy; 0uy|] |> ofBytes;;
// val b : int = 42

// ...or, the more usual case, you use in context where type can be inferred,
// like this example:
1 + ([|42uy; 0uy; 0uy; 0uy|] |> ofBytes);;
//val it : int = 43

How do generic functions work?

F# does not support overloaded functions, but it does support overloaded methods on types (classes) - including static methods. F#+ takes advantage of this by definining generic functions that call out to an internal class (referred to as an "Invokable") where various overloaded static methods are defined.

An Invokable is written such that the most specific, and hence, optimised overload is resolved for existing .Net and F# types, and that a more general implementation is used otherwise.

What does this all mean?

It means care is taken to use the most optimised implementation, and you can implement your own instances of generic functions if you implement the required methods.

Examples

Here are some examples of the generic map operation over existing .NET and F# types:

map string [|2;3;4;5|]
// val it : string [] = [|"2"; "3"; "4"; "5"|]

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

let res12 = map ((+) 9) (async {return 3})
// val it : Async<int> = Microsoft.FSharp.Control.FSharpAsync`1[System.Int32]
extract res12
// val it : int = 12

Here are some examples with types defined in this library:

open FSharpPlus.Data

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

let stateFul42 = map string (State (fun x -> (42, x)))
State.run stateFul42 "state"
// val stateFul42 : State<string,string> = State <fun:map@12-9>
// val it : string * string = ("42", "state")

Now let's define our own type with its own map definition

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)

For a type defined in an external library it will work when it contains a static member matching the expected name and signature.

Here's an example of the generic function fromBigInt targeting a type defined in the MathNet library

#r "../../packages/docs/MathNet.Numerics/lib/net40/MathNet.Numerics.dll"
#r "../../packages/docs/MathNet.Numerics.FSharp/lib/net45/MathNet.Numerics.FSharp.dll"

let x : MathNet.Numerics.BigRational = fromBigInt 10I
namespace FSharpPlus
val a: byte[]
val toBytes: value: 'a -> byte[] (requires member ToBytes)
<summary> Convert to a byte array value, assuming little endian </summary>
<category index="21">Converter</category>
val b: int
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>
val ofBytes: value: byte[] -> 'a (requires member OfBytes)
<summary> Convert from a byte array value, assuming little-endian </summary>
<category index="21">Converter</category>
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>
val res12: Async<int>
val async: AsyncBuilder
<summary>Builds an asynchronous workflow using computation expression syntax.</summary>
<example id="async-1"><code lang="fsharp"> let sleepExample() = async { printfn "sleeping" do! Async.Sleep 10 printfn "waking up" return 6 } sleepExample() |&gt; Async.RunSynchronously </code></example>
val extract: x: 'Comonad<'T> -> 'T (requires member Extract)
<summary> Extracts a value from a comonadic context. </summary>
<category index="17">Comonads</category>
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>
val stateFul42: State<string,string>
Multiple items
union case State.State: ('s -> 't * 's) -> State<'s,'t>

--------------------
module State from FSharpPlus.Data
<summary> Basic operations on State </summary>

--------------------
[<Struct>] type State<'s,'t> = | State of ('s -> 't * 's) static member ( *> ) : x: State<'S,'T> * y: State<'S,'U> -> State<'S,'U> static member (<!>) : f: ('T -> 'U) * x: State<'S,'T> -> State<'S,'U> static member ( <* ) : x: State<'S,'U> * y: State<'S,'T> -> State<'S,'U> static member (<*>) : f: State<'S,('T -> 'U)> * x: State<'S,'T> -> State<'S,'U> static member (>>=) : x: State<'S,'T> * f: ('T -> State<'S,'U>) -> State<'S,'U> static member Delay: body: (unit -> State<'S,'T>) -> State<'S,'T> static member Return: a: 'T -> State<'S,'T> static member TryFinally: State<'S,'T> * f: (unit -> unit) -> State<'S,'T> static member TryWith: State<'S,'T> * h: (exn -> State<'S,'T>) -> State<'S,'T> static member Using: resource: 'a2 * f: ('a2 -> State<'S,'T>) -> State<'S,'T> (requires 'a2 :> IDisposable) ...
<summary> Computation type: Computations which maintain state. <para> Binding strategy: Threads a state parameter through the sequence of bound functions so that the same state value is never used twice, giving the illusion of in-place update.</para><para> Useful for: Building computations from sequences of operations that require a shared state.</para> The <typeparamref name="'s" /> indicates the computation state, while <typeparamref name="'t" /> indicates the result.</summary>
val x: string
val run: State<'S,'T> -> ('S -> 'T * 'S)
<summary> Runs the state with an inital state to get back the result and the new state. </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&lt;'Key, 'Value&gt; </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: MathNet.Numerics.BigRational
namespace MathNet
namespace MathNet.Numerics
type BigRational = interface IComparable override GetHashCode: unit -> int override ToString: unit -> string static member ( * ) : BigRational * BigRational -> BigRational static member (+) : BigRational * BigRational -> BigRational static member (-) : BigRational * BigRational -> BigRational static member (/) : BigRational * BigRational -> BigRational static member (<) : BigRational * BigRational -> bool static member (<=) : BigRational * BigRational -> bool static member (<>) : BigRational * BigRational -> bool ...
val fromBigInt: x: bigint -> 'Num (requires member FromBigInt)
<summary>Converts from BigInteger to the inferred destination type.</summary>
<category index="22">Numerics</category>