这种类型注解是如何工作的,为什么另一个不行?

How this type annotation works, and why the other one does not?

请解释 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)) =
    ...

提前致谢。

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

由于 CLR 不支持此类检查,它们必须在编译时完成,F# 编译器很乐意为您完成,但它也有代价:因为没有 CLR 支持,所以无法将此类函数编译为 IL,这意味着每次与新的通用参数一起使用时它都必须是 "duplicated"(这种技术有时也称为“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.