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[]
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() |> 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<'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: 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>