F# error in generic constraint
Asked Answered
T

2

8

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

Trixie answered 20/2, 2018 at 3:45 Comment(1)
you want SRTPObligato
A
11

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.

Abutter answered 20/2, 2018 at 6:6 Comment(2)
Thanks for explaining the syntax! Do you know where that strange invocation syntax is documented? I checked the f# specifications and could not find where it is specified that parameters are to be passed as a tuple during invocation. It seems to be common knowledge but is not specified anywhere. Where did you learn this syntax ?Authors
In the F# spec 4.1 (the lastest as of this writing) these are called "member constraints" and are described in sections 5.2.3 (specification) and 6.4.8 (invocation).Abutter
O
4

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!

Obstreperous answered 20/2, 2018 at 12:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.