【问题标题】:How this type annotation works, and why the other one does not?这种类型注释是如何工作的,为什么另一个没有?
【发布时间】:2015-05-18 13:07:35
【问题描述】:

请解释drawShape 函数背后的魔力。 1) 为什么它会起作用 -- 我的意思是它如何调用 Draw 成员,2) 为什么它需要是 inline

type Triangle() =
    member x.Draw() = printfn "Drawing triangle"

type Rectangle() =
    member x.Draw() = printfn "Drawing rectangle"

let inline drawShape (shape : ^a) =
    (^a : (member Draw : unit->unit) shape)

let triangle = Triangle()
let rect = Rectangle()

drawShape triangle
drawShape rect

下一个问题是——是否可以使用下面的参数类型注释来编写drawShape 函数?我发现它的签名与第一个完全相同,但我无法完成正文。

let inline drawShape2 (shape : ^a when ^a : (member Draw : unit->unit)) =
    ...

提前致谢。

【问题讨论】:

  • 魔力全在 F# 编译器中 - 不知道该说什么 - 对于您的第二个问题:如果没有看到正文/错误,您很难分辨
  • 我尝试将shape.Draw() 放入正文中但没有成功。错误是:error FS0072: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.
  • 啊,我明白了 - 很遗憾,您必须再次重复您在 drawShap 中所做的相同操作((^a : (member Draw : ...) shape) 中的部分
  • 只是对此的评论:恕我直言,你真的应该只在绝对需要时才使用这个功能 - 在这里它会更容易拥有IDrawable 接口与抽象 Draw 方法代替。
  • 这正是我想要避免的——OO 启发的显式接口。 ;-)

标签: f#


【解决方案1】:

这种看起来像巫毒教的语法称为“statically resolved type parameter”。这个想法是要求编译器检查作为泛型参数传递的类型是否具有某些成员(在您的示例中 - Draw)。

由于 CLR 不支持此类检查,它们必须在编译时完成,F# 编译器很乐意为您完成,但它也有代价:因为没有 CLR 支持,所以没有办法要将此类函数编译为 IL,这意味着每次与新的通用参数一起使用时都必须“复制”它(这种技术有时也称为“monomorphisation”),这就是 inline 关键字的含义为了。

至于调用语法:出于某种原因,仅声明参数本身的约束并不能削减它。您需要在每次实际引用该成员时声明它:

// Error: "x" is unknown
let inline f (a: ^a when ^a: (member x: unit -> string)) = a.x() 

// Compiles fine
let inline f a = (^a: (member x: unit -> string)( a )) 

// Have to jump through the same hoop for every call
let inline f (a: ^a) (b: ^a) = 
  let x = (^a: (member x: unit -> string)( a ))
  let y = (^a: (member x: unit -> string)( b ))
  x+y

// But can wrap it up if it becomes too messy
let inline f (a: ^a) (b: ^a) = 
  let callX t = (^a: (member x: unit -> string) t)
  (callX a) + (callX b)

// This constraint also implicitly carries over to anybody calling your function:
> let inline g x y = (f x y) + (f y x)
val inline g : x: ^a -> y: ^a -> string when  ^a : (member x :  ^a -> string)

// But only if those functions are also inline:
> let g x y = (f x y) + (f y x)
Script.fsx(49,14): error FS0332: Could not resolve the ambiguity inherent in the use of the operator 'x' at or near this program point. Consider using type annotations to resolve the ambiguity.

【讨论】:

  • 感谢您的精彩解释和链接。所以,总结一下,这个(^a: (member x: unit -> string)( a )) 大致意思是“确保类型^a 包含成员x,这是一个返回字符串的无参数函数,并在实例a 上调用它,不带参数。
  • 关于成员约束另见:stackoverflow.com/questions/4694633/…
猜你喜欢
  • 2019-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-03
  • 2019-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多