7

Please explain the magic behind drawShape function. 1) Why it works at all -- I mean how it calls the Draw member, 2) why it needs to be 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

And the next issue is -- is it possible to write drawShape function using parameter type annotation like below? I found that it has exactly the same signature as the first one, but I'm unable to complete the body.

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

Thanks in advance.

Endrju
  • 2,354
  • 16
  • 23
  • the magic is all in the F# compiler - don't know what more to say - for your second issue: without seeing the body/error you have it's quite hard to tell – Random Dev May 18 '15 at 13:16
  • I tried just put `shape.Draw()` in the body without success. The error is: `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.` – Endrju May 18 '15 at 13:30
  • ah I see - well sadly you have to repeat the same thing you did in `drawShap` yet again (the part inside the `(^a : (member Draw : ...) shape)` – Random Dev May 18 '15 at 13:32
  • 2
    just a comment on this: *IMHO* you should really only use this *feature* when you absolutely have to - here it would be much easier to have a `IDrawable` interface with the abstract `Draw` method instead. – Random Dev May 18 '15 at 13:39
  • This is exactly what I'm trying to avoid -- OO inspired explicit interfaces. ;-) – Endrju May 18 '15 at 13:46

1 Answers1

13

This Voodoo-looking syntax is called "statically resolved type parameter". The idea is to ask the compiler to check that the type passed as generic argument has certain members on it (in your example - Draw).

Since CLR does not support such checks, they have to be done at compile time, which the F# compiler is happy to do for you, but it also comes with a price: because there is no CLR support, there is no way to compile such function to IL, which means that it has to be "duplicated" every time it's used with a new generic argument (this technique is also sometimes known as "monomorphisation"), and that's what the inline keyword is for.

As for the calling syntax: for some reason, just declaring the constraint on the parameter itself doesn't cut it. You need to declare it every time you actually reference the member:

// 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.
Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • Thank you for great explanation and the links. So, concluding, this `(^a: (member x: unit -> string)( a ))` roughly means "make sure that type `^a` contains member `x` which is a parameterless function returning string, and call it on instance `a` with no arguments. – Endrju May 18 '15 at 14:19
  • regarding member constraints see also: http://stackoverflow.com/questions/4694633/how-do-i-write-this-member-constraint-in-f – CaringDev May 18 '15 at 20:32