Generics F#通用方法和运算符

Generics F#通用方法和运算符,generics,f#,type-inference,Generics,F#,Type Inference,到目前为止,我对F#中的类型推断印象深刻,但是我发现了一些它没有真正得到的东西: //First up a simple Vect3 type type Vect3 = { x:float; y:float; z:float } with static member (/) (v1 : Vect3, s : float) = //divide by scalar, note that float {x=v1.x / s; y= v1.y /s; z = v1.z /s} sta

到目前为止,我对F#中的类型推断印象深刻,但是我发现了一些它没有真正得到的东西:

//First up a simple Vect3 type
type Vect3 = { x:float; y:float; z:float } with 
  static member (/) (v1 : Vect3, s : float) = //divide by scalar, note that float
    {x=v1.x / s; y= v1.y /s; z = v1.z /s}
  static member (-) (v1 : Vect3, v2 : Vect3) = //subtract two Vect3s
    {x=v1.x - v2.x; y= v1.y - v2.y; z=v1.z - v2.z}
  //... other operators...

//this works fine
let floatDiff h (f: float -> float) x = //returns float
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

//as does this
let vectDiff h (f: float -> Vect3) x = //returns Vect3
  ((f (x + h)) - (f (x - h)))/(h * 2.0)


//I'm writing the same code twice so I try and make a generic function:
let genericDiff h (f: float -> 'a) x : 'a = //'a is constrained to a float 
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

当我尝试构建最后一个函数时,在除号下会出现一个蓝色的波浪形符号,编译器会发出可怕的警告:“此构造导致代码没有类型注释所指示的那么通用。类型变量“a已被约束为”float“类型。”。我为Vect3提供了适合该功能的
/
运算符。为什么它会警告我?

标准的.NET泛型没有足够的表达能力来允许这种泛型函数。问题是,您的代码可以用于任何支持减法运算符的
'a
,但.NET泛型无法捕获此约束(它们可以捕获接口约束,但不能捕获成员约束)

但是,您可以使用F#
内联
函数,并且该函数可以具有额外的成员约束。我写了一篇关于这些的文章

简单地说,如果您将函数标记为
inline
,并让编译器推断类型,那么您会得到(我删除了对类型参数的显式提及,因为这使得情况更加棘手):

编译器现在使用
^a
而不是
'a
来表示参数是静态解析的(在内联期间),并且它添加了一个约束,表示
^a
必须有一个成员
-
,该成员接受两件事并返回
浮点值

遗憾的是,这并不是您想要的,因为
-
操作符返回
Vect3
(而不是编译器推断的
float
)。我认为问题在于编译器需要具有两个相同类型参数的
/
运算符(而您的是
Vect3*float
)。您可以使用不同的操作员名称(例如,
/。
):


在这种情况下,它将工作在<代码> VCT3(如果您重命名标量除法),但它将不容易工作在<代码>浮点< /代码>(虽然可能有黑客会使这成为可能)-虽然我不认为这是惯用的F,但是我可能会设法找到避免这种需求的方法。提供元素级除法并将

h
作为
Vect3
值传递可能有意义吗?

出于某种原因,编译器假定除法的类型签名是

^a*^a -> ^b
什么时候应该可以

^a*^c -> ^b
我相信9.7中暗示了这一点(这是度量单位,但我不明白为什么它们是特例)。如果部门可以具有所需的类型签名,您可以执行以下操作:

let inline genericDiff h (f: float -> ^a) x = 
    let inline sub a b = (^a: (static member (-):^a * ^a-> ^a) (a,b))
    let inline div a b = (^a: (static member (/):^a -> float-> ^c) (a,b))
    div (sub (f (x+h)) (f(x-h))) (h*2.0)
我已经包括了implicit
sub
div
,但它们不应该是必需的-目的是使签名明确


我认为,这不允许
^a
除了浮点数以外的任何东西,事实上这可能是一个错误。

如果您对文本使用通用数字,它会起作用:

让内联genericfifx=
设一=LanguagePrimitives.GenericOne
让二=一+一
((f(x+h))-(f(x-h))/(h*2)
genericDiff 1.0(乐趣y->{x=y;y=y;z=y})1.0/{x=1.0;y=1.0;z=1.0;}

我认为浮点文本,
2.0
是问题的根源。使用泛型数字,使用
/
的函数可以工作。@Daniel有趣。我想我试过类似的方法,但是编译器对
^t*float->^t
不满意。它确实适用于泛型文字(正如你在回答中所做的)。@Tomas感谢你的文章链接,我以前读过关于泛型的不同注释,但没有真正理解它们。这篇文章确实帮助我理解了它们,以及为什么它在数字代码中很有用@Daniel我不明白为什么文本应该约束返回类型,编译器是否假设
/
上的arg类型是相同的?如果其中一个类型是已知的(在本例中是float),它似乎在该类型上查找
静态成员(/)
,而如果两个类型都是泛型的,它似乎同时查找这两个类型(您所期望的)。简单示例:
让内联divByTwo x=x/2.0
x
约束为浮点,因为
float
/
定义为
float->float->float
。我认为这是一个bug,如果指定了inline,那么它应该检查这两种类型,如果其中一种类型仍然未知,那么在调用站点知道这两种类型之前,将其保留下来。我以前遇到过这个问题,将作为错误报告。或者,将
h*two
替换为
h+h
,以消除两个linesTrue。这是为了演示问题的一般解决方案。这似乎是一个奇怪的概念,必须从许多常数中建立更大的常数:)。我想一旦你有了两个,你就可以开始乘法了。如果常数是19,你会这样做:
让16=2*2*2*2;设myConst=16+2+1好好玩@Ciemnl或者,您可以使用类似于此答案中所示的代码,以避免必须乘以通用数字。
^a*^c -> ^b
let inline genericDiff h (f: float -> ^a) x = 
    let inline sub a b = (^a: (static member (-):^a * ^a-> ^a) (a,b))
    let inline div a b = (^a: (static member (/):^a -> float-> ^c) (a,b))
    div (sub (f (x+h)) (f(x-h))) (h*2.0)