Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# explicit member constraints: The type variable ^T could not be generalized because it would escape its scope

Tags:

generics

f#

I'm attempting to use explicit member constraints in F#. The documentation says "F# supports the complete set of constraints that is supported by the common language runtime", but if I actually compile a class with such an explicit constraint, such as the following, I get quite the exotic error.

type MyType<'T when ^T: (static member ( + ) : ^T * ^T -> ^T)> =
    member this.F a b = a + b

reports

error FS0670: This code is not sufficiently generic. The type variable ^T when ^T : (static member ( + ) : ^T * ^T -> ^T) could not be generalized because it would escape its scope.

And reports it at the site of defining member this.F. What does this mean? What is the relevant scope?

There are a number of approaches supported by the language for doing this sort of work. A nice exploration can be found here on StackOverflow, but I've not seen a clear explanation of why this particular generic constraint is not allowed to 'escape'.

like image 249
Sebastian Good Avatar asked May 05 '11 22:05

Sebastian Good


3 Answers

Member constrains need statically resolved type parameters. But statically resolved type parameters are not allowed on types (as in your example), only for inline functions and inline methods.

The underlying problem is probably that types as a whole cannot be inline.

See also: http://msdn.microsoft.com/en-us/library/dd548046.aspx

If you use an inline member like this:

type MyType() =
    member inline this.F a b = a + b

the types of a and b will automatically be correctly constrained.

like image 188
wmeyer Avatar answered Nov 06 '22 23:11

wmeyer


You need to mark the member inline (and add type annotations if you want to force the arguments to be of type ^T):

type MyType<'T when ^T: (static member ( + ) : ^T * ^T -> ^T)>() =   
    member inline this.F (a:^T) (b:^T)  = a + b

I've also added a constructor, so that you can actually call the method:

MyType().F 1 2

As others have noted, it is rarely necessary to write out the explicit member constraints by hand since they will usually be inferred. Furthermore, in this case there's no reason to put the constraint on the type rather than the method, and having a type parameterized by a statically resolved type variable is not idiomatic.

like image 31
kvb Avatar answered Nov 06 '22 22:11

kvb


F# specification:

A type of the form ^ident is a statically resolved variable type. A fresh type inference variable is created and added to the type inference environment (see §14.6). This type variable is tagged with an attribute indicating it may not be generalized except at inline definitions (see §14.7), and likewise any type variable with which it is equated via a type inference equation may similarly not be generalized.

http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.html

like image 28
manojlds Avatar answered Nov 06 '22 22:11

manojlds