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.


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 array
val toBytes: value: 'a -> byte array (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)

type int = int32

type int<'Measure> = int
val ofBytes: value: byte array -> 'T (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

type string = System.String
union case Option.Some: Value: 'T -> Option<'T>
val res12: Async<int>
val async: AsyncBuilder
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 (>=>) : f: ('T -> State<'S,'U>) * g: ('U -> State<'S,'V>) -> ('T -> State<'S,'V>) 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> ...
<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

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> ...

new: elements: seq<'Key * 'Value> -> Map<'Key,'Value>
val x: Tree<'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: obj
val fromBigInt: x: bigint -> 'Num (requires member FromBigInt)
<summary>Converts from BigInteger to the inferred destination type.</summary>
<category index="22">Numerics</category>