Reputation: 2238
I found a nice article: http://nut-cracker.azurewebsites.net/blog/2011/08/09/operator-overloading/
and decided to play a bit with F# using the information presented in the article. I created my own type Vector2D<'a>, supporting addition and multiplication by a constant:
type Vector2D<'a> = Vector2D of 'a * 'a
with
member this.X = let (Vector2D (x,_)) = this in x
member this.Y = let (Vector2D (_,y)) = this in y
static member inline (+) (Vector2D (lhsx, lhsy), Vector2D (rhsx, rhsy)) =
Vector2D(lhsx + rhsx, lhsy + rhsy)
static member inline ( * ) (factor, Vector2D (x, y)) =
Vector2D (factor * x, factor * y)
static member inline ( * ) (Vector2D (x, y), factor) =
Vector2D (factor * x, factor * y)
That works fine - I haven't observed any problems while using these members. Then I decided to add support for dividing by a constant as well (with intend to implement e.g. normalizing vectors):
static member inline (/) (Vector2D (x, y), factor) =
Vector2D (x / factor, y / factor)
The thing is even though it compiles fine, every time I try to use it, I see an error:
let inline doSomething (v : Vector2D<_>) =
let l = LanguagePrimitives.GenericOne
v / l // error
The error message is:
A type parameter is missing a constraint 'when ( ^a or ^?18259) : (static member ( / ) : ^a * ^?18259 -> ^?18260)'
Moreover - the doSomething's infered type is: Vector2D<'a> -> Vector2D<'c> (requires member (/) and member (/) and member get_One)
My question is: why it's possible to use the members (*) and (+), but it's impossible to do so with (/) even though their defined in the same way ? Also, why the infered type says it needs (/) member twice ?
Upvotes: 4
Views: 240
Reputation: 26174
It says requires /
twice because the types are not necessary the same, so your function is going to be more generic than you expect.
When designing generic math libraries some decisions must be taken. You can't just go ahead without type annotating everything or restricting math operators, type inference will be unable to come up with the types.
The problem is that F# arithmetic operators are not restricted, they have an 'open' signature: 'a->'b->'c
so either you restrict them or you will have to full type annotate all your functions because if you don't F# will be unable to know which overload should take.
If you ask me I will take the first approach (which is also the Haskell approach), it's a bit complicated at the beginning but once you have everything set up is quite fast to implement new generic types and its operations.
Explaining this will require another post in that blog (I will someday) but I'll give you a short example:
// math operators restricted to 'a->'a->'a
let inline ( + ) (a:'a) (b:'a) :'a = a + b
let inline ( - ) (a:'a) (b:'a) :'a = a - b
let inline ( * ) (a:'a) (b:'a) :'a = a * b
let inline ( / ) (a:'a) (b:'a) :'a = a / b
type Vector2D<'a> = Vector2D of 'a * 'a
with
member this.X = let (Vector2D (x,_)) = this in x
member this.Y = let (Vector2D (_,y)) = this in y
static member inline (+) (Vector2D (lhsx, lhsy), Vector2D (rhsx, rhsy)) =
Vector2D(lhsx + rhsx, lhsy + rhsy)
static member inline (*) (Vector2D (x1, y1), Vector2D (x2, y2)) =
Vector2D (x1 * x2, y1 * y2)
static member inline (/) (Vector2D (x1, y1), Vector2D (x2, y2)) =
Vector2D (x1 / x2, y1 / y2)
static member inline get_One () :Vector2D<'N> =
let one:'N = LanguagePrimitives.GenericOne
Vector2D (one, one)
let inline doSomething1 (v : Vector2D<_>) = v * LanguagePrimitives.GenericOne
let inline doSomething2 (v : Vector2D<_>) = v / LanguagePrimitives.GenericOne
So the idea is not to define all overloads involving your type with numeric types, instead define conversions from numeric types to your type and only the operations between your type.
Now if you want to multiply your vector with another vector or an integer you use the same overload in both cases:
let inline multByTwo (vector:Vector2D<_>) =
let one = LanguagePrimitives.GenericOne
let two = one + one
vector * two // picks up the same overload as for vector * vector
Of course now you get into the problem of generating generic numbers which is another topic closely related. There are many (different) answers here at SO regarding how to generate a NumericLiteralG module. F#+ is a library that provides an implementation of such module, so you can write vector * 10G
You may wonder why F# operators are not restricted as is the case in other languages like Haskell. This is because some existing .NET types are already defined this way, a simple example is DateTime which support addition of an integer which is arbitrary decided to represent a day, see more discussion here.
UPDATE
To answer your follow up question about how to multiply by floating point number, again this is a whole topic: How to create generic numbers with many possible solutions depending on how generic and scalable your library you want to be. Anyway here is a simple way to get some of that functionality:
let inline convert (x:'T) :'U = ((^T or ^U): (static member op_Explicit : ^T -> ^U) x)
let inline fromNumber x = // you can add this function as a method of Vector2D
let d = convert x
Vector2D (d, d)
let result1 = Vector2D (2.0f , 4.5f ) * fromNumber 1.5M
let result2 = Vector2D (2.0 , 4.5 ) * fromNumber 1.5M
F#+ does something like this but with rational numbers instead of decimal in order to preserve more precision.
Upvotes: 3