Maik Klein
Maik Klein

Reputation: 16158

Type constraints for static methods

I am using OpenTK with its math library but unfortunately there is no generic interface for the vector classes. For example Vector2 ,3 and 4 all have the same static method SizeInBytes http://www.opentk.com/files/doc/struct_open_t_k_1_1_vector3.html#ae7cbee02af524095ee72226d842c6892

Now I could just overload tons of different constructors but I think it should be possible to solve this via type constraints.

I was reading though http://msdn.microsoft.com/en-us/library/dd233203.aspx and I found this

type Class4<'T when 'T : (static member staticMethod1 : unit -> 'T) > =
    class end

Now I have tried it by myself but I can't get the syntax right.

type Foo<'T when 'T: (static member SizeInBytes: unit -> int)>(data: 'T []) =
   member this.GetBytes() = 'T.SizeInBytes() 

let f = Foo([|new Vector3(1.0f,1.0f,1.0f)|])
f.GetBytes()

Can you spot the problem?

Edit: VS2012 complains about this line 'T.SizeInBytes() //Unexpected symbol or expression and T.SizeInBytes() doesn't work either.

Edit2:

I made an example that doesn't involve an external library

type Bar() = 
    static member Print() = printf "Hello Foo"

type Foo<'T when 'T: (static member Print: unit -> unit)>(data: 'T []) =
   member this.Print() = 'T.Print()

let b1 = Bar()
let f = Foo([|b1|])
f.Print()

Upvotes: 4

Views: 876

Answers (2)

Ganesh Sittampalam
Ganesh Sittampalam

Reputation: 29110

The correct syntax for calling things which are guaranteed by member constraints is a bit obscure:

type Foo< ^T when ^T: (static member SizeInBytes: unit -> int)>(data: ^T []) =
   member inline this.GetBytes() =
       (^T : (static member SizeInBytes : unit -> int) ())

Note that 'T has to be changed to a "statically resolved type variable" ^T - see the Glossary in the F# spec.

You can't call members specified by constraints on normal type variables, because that's not supported by the .NET framework, so F# has to compile them away. It's a syntax error if we try to use 'T in GetBytes instead.

I think the MSDN documentation is being a bit misleading by giving an example with 'T, because although you can write the type they give, you could never use the constraint.

If you look at the IL code for the Class4 sample, the constraint is actually gone:

.class nested public auto ansi serializable Class4`1<T>
    extends [mscorlib]System.Object

which makes sense because the member constraint has to be removed for .NET. The same is true for type Foo with the ^T type variable.

Note also that in common with all inline F# functions, you can only call it statically from F# code, so that the compiler can inline the definition at the call site.

It will throw an exception if you try to call it from C# code, or via reflection. If you try, your code will fail at runtime.

Generally working with F# constraints that aren't supported by .NET is a tricky business, so I'd steer clear if at all possible.

EDITED: I've substantially updated my original answer which incorrectly said this wasn't possible, in light of (a) my further experiments (b) Gene Belitski's answer and (c) idjarn's comment that inline functions always get compiled to IL that throws an exception.

Upvotes: 7

Gene Belitski
Gene Belitski

Reputation: 10350

Worked for me just fine using hat notation:

type Bar() =
    static member SizeInBytes() = 42

type Foo< ^T when ^T: (static member SizeInBytes: unit -> int)>(data: ^T []) =
    member inline this.GetBytes () = (^T : (static member SizeInBytes : unit -> int) ())

let result = (Foo([|Bar()|]).GetBytes())

val result : int = 42

Upvotes: 4

Related Questions