Ed *_*ers 4 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}
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)
Run Code Online (Sandbox Code Playgroud)
当我尝试构建最后一个函数时,在分号后面会出现一个蓝色的波浪线,并且编译器会说"这个构造导致代码比类型注释所指示的更不通用"的可怕警告.类型变量'a被限制为输入'float'".我为Vect3提供了适合/该功能的操作符.为什么要警告我?
标准的.NET泛型不足以表达这种泛型函数.问题是你的代码可以用于任何'a支持减法运算符的代码,但.NET泛型不能捕获这个约束(它们可以捕获接口约束,但不能捕获成员约束).
但是,您可以使用F#inline函数和静态解析的类型参数,这些参数可以具有其他成员约束.我写了一篇文章,提供了一些关于这些的更多细节.
简而言之,如果你将函数标记为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)
Run Code Online (Sandbox Code Playgroud)
编译器现在使用^a而不是'a说参数是静态解析的(在内联期间)并且它添加了一个约束,说明^a必须有一个成员-需要两件事并返回float.
遗憾的是,这不是你想要的,因为你的-运算符返回Vect3(而不是float编译器推断).我认为问题是编译器希望/运算符有两个相同类型的参数(而你的是Vect3 * float).您可以使用不同的运营商名称(例如/.):
let inline genericDiff2 h (f: float -> _) x =
((f (x + h)) - (f (x - h))) /. (h * 2.0);;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,它将工作Vect3(如果你重命名标量部分),但它不会轻松工作float(虽然可能有黑客可以使这成为可能 - 看到这个答案 - 虽然我不会考虑这个惯用的F#和我可能会试图找到一种方法来避免这种需要).提供元素划分和传递h作为Vect3值是否有意义?
如果您为文字使用通用数字,它的工作原理如下:
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;}
Run Code Online (Sandbox Code Playgroud)