The following F# code
let f<'T when 'T: (member Id:int)> (t:'T) = t.Id
is not accepted with the following error:
Error FS0670 This code is not sufficiently generic. The type variable
^T when ^T : (member get_Id : ^T -> int) could not be generalized
because it would escape its scope.
What's wrong?
How to fix it?
EDIT
#Fyodor: tricky! I did some tests and found more strangeness:
let inline f1<^T when ^T: (member Id:int)> (t:^T) = ( ^T: (member Id:int) t )
let inline f2<'T when 'T: (member Id:int)> (t:'T) = ( 'T: (member Id:int) t )
let inline f3<'T when 'T: (member Id:int)> (t:'T) = ( ^T: (member Id:int) t )
let inline f4 t = ( ^T: (member Id:int) t )
f1 gives error in <^T
Error FS0010 Unexpected infix operator in pattern
f2 gives errors in ( 'T
Error FS0583 Unmatched '('
Error FS0010 Unexpected quote symbol in binding
f3 and f4 are accepted
You have three mistakes:
First, the function needs to be inline. .NET CLR doesn't currently support member constraints (i.e. "this can be any type as long as it has this member"), which means that such function cannot be compiled to IL, so the F# compiler has to fake it and substitute these functions at compile time. To signal the compiler that you know this and agree, you have to add the inline keyword right after let. Inline functions will be completely erased at compile time and will not show up as CLR methods in compiled code.
Second, the generic parameter name needs to be prefixed with ^ instead of '. This is actually optional, since the compiler seems to be automatically replacing ' with ^ (as evident from your error message), but just for consistency. The generic parameters prefixed with ^ are called "statically resolved type parameters", which refers to the fact that they get resolved (and erased) at compile time, as described above.
Third, the syntax for referencing such members in the function body is not actually the same as the syntax for referencing regular members. You can't use the dot notation. Instead you have to use this weird syntax that mirrors the parameter declaration.
Applying all three fixes, this would be your new code:
let inline f<^T when ^T: (member Id:int)> (t:^T) = ( ^T: (member Id:int) t )
Note that, since the member constraint is now in the function body, it does not have to be repeated on the left of =, so you can write just this:
let inline f t = ( ^T: (member Id:int) t )
Here's a bit more info on this.
To add a brief comment regarding the edit that you added - the problem with your definition of f1 is simply that the parser requires a space between the angle brackets and the hat in: <^:
let inline f1< ^T when ^T: (member Id:int)> (t:^T) = ( ^T: (member Id:int) t )
Otherwise, the syntax <^ gets parsed as an operator, rather than generic argument list, which is what you need here. All other information is in Fyodor's answer!
Related
From an answer by kvb about how to call implicit conversion operators:
let inline (!>) (x:^a) : ^b = ((^a or ^b) : (static member op_Implicit : ^a -> ^b) x)
I've known F# for a while but I just don't know how to parse the implementation here. What is (^a or ^b)? And the stuff after that? Please go over what each part represents grammatically.
^a or ^b means literally "^a or ^b".
The colon : means "has" or "contained in", depending on how you look at it.
So the whole expression (^a or ^b) : (static member op_Implicit : ^a -> ^b) means "static member named "op_Implicit" that has type ^a -> ^b and is defined on either type ^a or type ^b". This whole expression ultimately evaluates to a function of type ^a -> ^b.
Then, the x placed to the right of that expression means "function application", just like in the usual F# syntax.
So the whole thing taken together would mean "on type ^a or type ^b, find a static member named "op_Implicit" that has type ^a -> ^b, and apply that member to argument x".
For a bit more discussion of statically resolved constraints, see this answer or this MSDN article.
What does "^" mean when it's in front of a type?
Example:
int : ^T -> int
string : ^T -> string
this indicates an Statically Resolved Type Parameter
from MSDN:
A statically resolved type parameter is a type parameter that is
replaced with an actual type at compile time instead of at run time.
They are preceded by a caret (^) symbol.
so it's very similar to 'T but you can use it to give member constraints and the compiler will resolve them at compile-time (obviously) - usually you are just using inline and the type-inference will work it out for you - but there are some quite advanced tricks (for example FsControl) out there using this (not often used) feature
example
let inline add a b = a + b
val inline add :
a: ^a -> b: ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
will add such a constraint to indicate that this will work with all numeric types (it will add an member constraint to an static operator (+))
There are two snippets of F# I would like to understand, but don't know what to google. First:
let ``1+2`` () = ....
I am guessing this just means "turn the expression into an identifier"? But what is that feature called if I want to refer to it?
Second, what does the character ^ mean when it occurs in a type? I have found several mentions of it, but the explanation always just says "the type is this" rather than "it differs from a type without a 1^1 in that ...". For example:
let inline blah x y = x+y;;
val inline blah :
^a -> ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
Many thanks in advance.
I'd probably call that a "quoted identifier" http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.html#_Toc270597387
"Statically resolved type parameter" http://msdn.microsoft.com/en-us/library/dd548046%28VS.100%29.aspx
The backquote syntax is indeed just a way to 'quote' arbitrary characters into identifiers, I am not sure if it has a name. It is typically used for e.g.
let ``This Identifier Contains Spaces`` = 42
or
foo.``member``(42) // 'member' is an F# keyword, but maybe it was the name of some
// method from C# code you're using, so here's a way to call it
The carat indicates a statically resolved type parameter:
http://msdn.microsoft.com/en-us/library/dd548046.aspx
used for ad-hoc overloading/genericity.
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'.
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.
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.
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
I am trying to explore the type of operators such as :: in F# interactive.
But I get these kinds of messages:
Unexpected symbol '::' in expression. Expected ')' or other token.
even if I surround it with (::).
I do it like this:
> let inline showmecons a b = a :: b;;
val inline showmecons : 'a -> 'a list -> 'a list
or
> let inline showmepow a b = a ** b;;
val inline showmepow :
^a -> ^b -> ^a when ^a : (static member Pow : ^a * ^b -> ^a)
You'll see the type of usual operators if you surround them with parentheses:
> (+);;
val it : (int -> int -> int) = <fun:it#4-5>
Unfortunatelly, this restricts the type of the operator to one specific type - F# Interactive doesn't print the polymorphic definition (with constraints). You can use the workaround suggested by Stephen (and define a new inline function) to see that.
The reason why it doesn't work for :: is that :: is actually a special syntactic construct (defined directly in the F# specification).
This is pretty old, but I'm learning F# and also wanted to figure this out.
Looking at the F# specification on page 32, we see that symbolic keywords also have a compiled name in F#. The equivalent compiled name for :: is op_ColonColon, which actually accepts tuples:
> op_ColonColon;;
val it : arg0:'a * arg1:'a list -> 'a list = <fun:clo#22-5>`
Using :: to define an inline function will give us a curried cons function, which is misleading, I believe.