为什么F#约束我的代码并删除泛型?

时间:2014-02-04 07:30:45

标签: f#

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#尝试约束我的代码,但我希望它是通用的。我真的不关心数据的类型,我只需要传递正确的dataSize。

我做错了什么?

3 个答案:

答案 0 :(得分:5)

如果F#编译器在同一代码块中的特定实例中使用,它们似乎专门用于泛型类型。尝试拆分你的声明:

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)

答案 1 :(得分:3)

F#的类型推理从上到下和从左到右进行,偶尔会导致有趣的角落情况。特别是,类型中的重新排序方法定义可以影响推断类型。正如Ganesh指出的那样,你的问题的一个解决方案是将你的类型分解为不相交的块,但这实际上并不是必需的 - 只需将泛型方法放在第一位就足够了(并注意你也可以删除类型注释) data,如果需要的话。)

您可以看到与简单let rec绑定非常相似的行为。考虑:

let rec f() = h 5
and g() = h "test"
and h x = x

在尝试推断f的类型时,编译器会注意h必须将int作为输入,这会使g的定义无效。重新排序定义以放置h是最简单的解决方案(然后编译器可以在查看hf的主体之前推断出g的泛型类型,所以一切经历好的)。

或者,您可以显式地使h泛型并添加类型注释以帮助编译器输出:

let rec f() = h 5
and g() = h "test"
and h<'t> (x:'t) :'t = x

这可能不方便,因为它可能需要大量的注释开销,但在某些情况下,简单的重新排序定义不足以让编译器推断出正确的类型,因此有时可能是必要的。

答案 2 :(得分:2)

我认为最好查看代码以了解出现了什么问题。

所以这是一个更简单的例子,显示了同样的问题。

type test = 
    static member test (data:int) = test.actual(data)
    static member test (data:float) =test.actual(data)
    static member actual (data:'t) = ()

问题在于,对于静态成员函数,需要知道所有类型 - 或者您需要泛型类型。

我认为最简单的解决方案是将代码更改为

let actual (data:'t) = ()

type test = 
    static member test (data:int) = actual(data)
    static member test (data:float) =actual(data)

在这里,编译器可以更自由地更改let绑定以使其成为通用。