4

到目前为止,我对 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 已被限制为输入‘浮动’”。我为 Vect3 提供了适合/该功能的运算符。为什么要警告我?

4

3 回答 3

6

标准 .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)

编译器现在使用^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(尽管可能会有黑客使这成为可能 -请参阅这个答案- 尽管我不会认为惯用的 F# 和我可能会尝试找到一种方法来避免这种情况)。h提供按元素划分并作为Vect3值传递是否有意义?

于 2012-08-10T12:13:55.423 回答
5

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

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-10T14:11:30.703 回答
1

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

^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)

我已经包含了隐含sub的,div但它们不应该是必需的 - 想法是使签名明确。

^a我相信除了浮动之外不允许其他任何东西的事实实际上可能是一个错误。

于 2012-08-10T12:48:20.550 回答