Generics F#通用方法和运算符
到目前为止,我对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
//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)
我已经包括了implicitsub
和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)