Reputation: 16158
type VBO<'T when 'T : (new : unit -> 'T) and 'T : struct and 'T :> ValueType> =
{ Handle : int
target : BufferTarget
size : int
dataSize : int
data : 'T []
pos : int
usage : BufferUsageHint }
type VBO =
static member Create(target, size, pos, usage, (data : Vector3 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
// Type mismatch. Expecting Vector3 but found Vector2
static member Create(target, size, pos, usage, (data : Vector2 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
// This construct causes code to be less generic than indicated by the type annotations.
// The type variable 'T has been constrained to be type 'Vector3'.
static member CreateImpl(target, size, pos, usage, dataSize, (data : 'T [])) =
let h = GL.GenBuffer()
{ Handle = h
target = target
size = size
dataSize = dataSize
data = data
pos = pos
usage = usage }
F# tries to constrain my code but I want it to be generic. I don't really care about what type the data is I just need it pass in the correct dataSize.
What have I done wrong?
Upvotes: 3
Views: 102
Reputation: 55195
F#'s type inference works top-to-bottom and left-to-right, which occasionally leads to interesting corner cases. In particular, reordering method definitions within a type can affect the inferred types. As Ganesh has pointed out, one solution to your problem is to break your type up into disjoint chunks, but this isn't actually necessary - just putting the generic method first should be sufficient (and note that you can also drop the type annotation on data
, if desired).
You can see very similar behavior with simple let rec
bindings. Consider:
let rec f() = h 5
and g() = h "test"
and h x = x
When attempting to infer the type of f
, the compiler notes that h
must take an int
as input, which makes g
's definition invalid. Reordering the definitions to put h
first is the easiest solution (then the compiler can infer a generic type for h
before looking at the bodies of f
and g
, so everything goes through fine).
Alternatively, you can explicitly make h
generic and add type annotations to help the compiler out:
let rec f() = h 5
and g() = h "test"
and h<'t> (x:'t) :'t = x
This can be inconvenient since it can require a significant annotation overhead, but there are circumstances where simply reordering definitions is insufficient to allow the compiler to infer the correct types, so it may sometimes be necessary.
Upvotes: 3
Reputation: 25526
I think it is better to look at the code to understand what went wrong.
So here is a simpler example that shows the same problem.
type test =
static member test (data:int) = test.actual(data)
static member test (data:float) =test.actual(data)
static member actual (data:'t) = ()
the problem is that for static member functions, all the types need to be known - or you need a generic type.
I think the simplest solution is to change your code to look like
let actual (data:'t) = ()
type test =
static member test (data:int) = actual(data)
static member test (data:float) =actual(data)
Here, the compiler has much more freedom to alter the let binding to make it generic.
Upvotes: 2
Reputation: 29110
The F# compiler seems to specialise generic types if they are used at a particular instantiation in the same block of code. Try splitting up your declaration like this:
type VBO<'T when 'T : (new : unit -> 'T) and 'T : struct and 'T :> ValueType> =
{ Handle : int
target : BufferTarget
size : int
dataSize : int
data : 'T []
pos : int
usage : BufferUsageHint }
type VBO =
static member CreateImpl(target, size, pos, usage, dataSize, (data : 'T [])) =
let h = GL.GenBuffer()
{ Handle = h
target = target
size = size
dataSize = dataSize
data = data
pos = pos
usage = usage }
type VBO with
static member Create(target, size, pos, usage, (data : Vector3 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
static member Create(target, size, pos, usage, (data : Vector2 [])) =
VBO.CreateImpl(target, size, pos, usage, Vector2.SizeInBytes, data)
Upvotes: 5