到目前为止,我对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提供了适用于该函数的/操作符。为什么它要警告我?
发布于 2012-08-10 20:13:55
标准的.NET泛型的表现力不足以支持这种泛型函数。问题是,您的代码可以用于任何支持减法运算符的'a,但.NET泛型无法捕获此约束(它们可以捕获接口约束,但不能捕获成员约束)。
但是,您可以使用可以具有附加成员约束的F#、inline函数和statically resolved type parameters。我写了一个关于这些的article that provides some more details。
简而言之,如果你将函数标记为inline,并让编译器推断类型,那么你会得到(我去掉了对类型参数的明确提及,因为这会使情况变得更加棘手):
> let inline genericDiff h (f: float -> _) x =
> ((f (x + h)) - (f (x - h))) / (h * 2.0);;
val inline genericDiff :
float -> (float -> ^a) -> float -> float
when ^a : (static member ( - ) : ^a * ^a -> float)编译器现在使用^a而不是'a来说明参数是静态解析的(在内联过程中),并且添加了一个约束,说明^a必须有一个成员-,该成员接受两项内容并返回float。
遗憾的是,这并不是您想要的结果,因为-操作符返回Vect3 (而不是编译器推断的float )。我认为问题在于编译器需要具有相同类型的两个参数的/运算符(而您的是Vect3 * float)。您可以使用不同的运算符名(例如,/.):
let inline genericDiff2 h (f: float -> _) x =
((f (x + h)) - (f (x - h))) /. (h * 2.0);;在这种情况下,它可以在Vect3上工作(如果你重命名标量除法),但它在float上不会是一件愚蠢的事情(尽管可能有一些黑客可以实现这一点- see this answer -尽管我不认为这是惯用的F#,我可能会尝试找到一种方法来避免这种需要)。提供元素除法并将h作为Vect3值传递是否有意义?
发布于 2012-08-10 22:11:31
如果您对文字使用通用数字,它将起作用:
let inline genericDiff h f x =
let one = LanguagePrimitives.GenericOne
let two = one + one
((f (x + h)) - (f (x - h))) / (h * two)
genericDiff 1.0 (fun y -> {x=y; y=y; z=y}) 1.0 //{x = 1.0; y = 1.0; z = 1.0;}发布于 2012-08-10 20:48:21
出于某种原因,编译器假定division的类型签名为
^a*^a -> ^b当它应该是可以的时候
^a*^c -> ^b我相信spec中的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)我已经包含了隐式的sub和div,但它们不应该是必需的-其想法是使签名显式。
我认为这不允许^a成为浮点数以外的任何东西,这一事实可能实际上是一个错误。
https://stackoverflow.com/questions/11901394
复制相似问题