Looking at FSharpPlus I was thinking to how to create a generic function to be used in
let qr0 = divRem 7 3
let qr1 = divRem 7I 3I
let qr2 = divRem 7. 3.
and came out with a possible (working) solution
let inline divRem (D:^T) (d:^T): ^T * ^T = let q = D / d in q, D - q * d
then I looked at how FSharpPlus implemented it and I found:
open System.Runtime.InteropServices
type Default6 = class end
type Default5 = class inherit Default6 end
type Default4 = class inherit Default5 end
type Default3 = class inherit Default4 end
type Default2 = class inherit Default3 end
type Default1 = class inherit Default2 end
type DivRem =
inherit Default1
static member inline DivRem (x:^t when ^t: null and ^t: struct, y:^t, _thisClass:DivRem) = (x, y)
static member inline DivRem (D:'T, d:'T, [<Optional>]_impl:Default1) = let q = D / d in q, D - q * d
static member inline DivRem (D:'T, d:'T, [<Optional>]_impl:DivRem ) =
let mutable r = Unchecked.defaultof<'T>
(^T: (static member DivRem: _ * _ -> _ -> _) (D, d, &r)), r
static member inline Invoke (D:'T) (d:'T) :'T*'T =
let inline call_3 (a:^a, b:^b, c:^c) = ((^a or ^b or ^c) : (static member DivRem: _*_*_ -> _) b, c, a)
let inline call (a:'a, b:'b, c:'c) = call_3 (a, b, c)
call (Unchecked.defaultof<DivRem>, D, d)
let inline divRem (D:'T) (d:'T) :'T*'T = DivRem.Invoke D d
I am sure that there are good reasons to make it as such; however I am not interested in why it's been done like that, but:
How does this work?
Is there any documentation helping to understand how this syntax works, especially the three DivRem static method overloads?
EDIT
So, the FSharp+ implementation has the advantage that, if the numeric type used in the divRem call implements a DivRem static member (like BigInteger for example), it will be used in place of the possibly existing arithmetic operators. This, assuming that DivRem is more efficient than calling the default operators, would make divRem optimal in efficiency. Yet a question remains:
why do we need to introduce the "ambiguity" (o1)?
Let's call the three overloads o1, o2, o3
If we comment out o1 and call divRem with a numeric parameter whose type doesn't implement DivRem (e.g. int or float), then o3 can't be used because of the member constraint. The compiler could choose o2, but it doesn't, like it said "you have a perfect signature matching overload o3 (so I will ignore the less than perfect signature in o2) but the member constraint is not fulfilled". Therefore, if I uncomment o1, I would expect it to say "you have two perfect signature overloads (so I will ignore the less than perfect signature in o2) but both of them have unfulfilled constraints". Instead it appears to say "you have two perfect signature overloads but both of them have unfulfilled constraints, so I will take o2 that, even with a less than perfect signature, can do the job". Wouldn't it be more correct to avoid the o1 trick and let the compiler say "your perfect signature overload o3 has an unfulfilled member constraint, so I take o2 which is less than perfect in signature but can do the job" even in the first instance?