Maik Klein
Maik Klein

Reputation: 16158

Why does F# constrain my code and remove generics?

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

Answers (3)

kvb
kvb

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

John Palmer
John Palmer

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

Ganesh Sittampalam
Ganesh Sittampalam

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

Related Questions