首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >F#泛型方法和运算符

F#泛型方法和运算符
EN

Stack Overflow用户
提问于 2012-08-10 20:10:21
回答 3查看 1.4K关注 0票数 4

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

代码语言:javascript
运行
复制
//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提供了适用于该函数的/操作符。为什么它要警告我?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2012-08-10 20:13:55

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

但是,您可以使用可以具有附加成员约束的F#、inline函数和statically resolved type parameters。我写了一个关于这些的article that provides some more details

简而言之,如果你将函数标记为inline,并让编译器推断类型,那么你会得到(我去掉了对类型参数的明确提及,因为这会使情况变得更加棘手):

代码语言:javascript
运行
复制
> 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)。您可以使用不同的运算符名(例如,/.):

代码语言:javascript
运行
复制
let inline genericDiff2 h (f: float -> _) x = 
  ((f (x + h)) - (f (x - h))) /. (h * 2.0);;

在这种情况下,它可以在Vect3上工作(如果你重命名标量除法),但它在float上不会是一件愚蠢的事情(尽管可能有一些黑客可以实现这一点- see this answer -尽管我不认为这是惯用的F#,我可能会尝试找到一种方法来避免这种需要)。提供元素除法并将h作为Vect3值传递是否有意义?

票数 6
EN

Stack Overflow用户

发布于 2012-08-10 22:11:31

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

代码语言:javascript
运行
复制
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;}
票数 5
EN

Stack Overflow用户

发布于 2012-08-10 20:48:21

出于某种原因,编译器假定division的类型签名为

代码语言:javascript
运行
复制
^a*^a -> ^b

当它应该是可以的时候

代码语言:javascript
运行
复制
^a*^c -> ^b

我相信spec中的9.7就暗示了这一点(这是针对度量单位的,但我不明白它们为什么是特例)。如果除法可以有你想要的类型签名,你可以这样做:

代码语言:javascript
运行
复制
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)

我已经包含了隐式的subdiv,但它们不应该是必需的-其想法是使签名显式。

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11901394

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档