Inspired by the implementation of the plus function (equivalent to mappend) in FSharpPlus, I decided to copy the pattern and implement a generic function incrementing numbers (it would probably be easier to use LanguagePrimitives, but it's just an exercise).
type NumberExtensions =
static member Increment (x:float) = x + 1.0
static member Increment (x:int) = x + 1
//etc
let inline increment number =
let inline call (_:^E) (number:^N) = ((^E or ^N) : (static member Increment: ^N -> ^N) number)
call (Unchecked.defaultof<NumberExtensions>) number //!
let incrementedFloat = increment 100.0
let incrementedInt = increment 200
It works as expected:
val incrementedFloat : float = 101.0
val incrementedInt : int = 201
Unfortunately, I don't quite get why do we need or ^N in the constraint. As far as I understand, the constraint says:
There's a static method named
Increment, which takes an argument of some type (the same type as thenumber's) and returns another value of the same type. This method must be defined either in^Eor in^N.
Here I thought: We know that the Increment method will be defined in ^E, so we can safely change (^E or ^N) to just ^E. Unfortunately, the change resulted in the following error in the line marked with !:
A unique overload for method 'Increment' could not be determined based on type information prior to this program point. A type annotation may be needed. Candidates: static member NumberExtensions.Increment: x:float->float, static member NumberExtensions.Increment: x:int->int
Why does adding or ^N to the constraint removes this ambiguity? Does it tell the compiler something else than the possible location of the method?