请解释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)) =
...
提前致谢。
答案 0 :(得分:13)
这种具有Voodoo外观的语法称为" 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.