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