Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# int.MaxValue is "not a valid constant expression," but System.Int32.MaxValue is?

TL;DR: the F# compiler interprets int in this context as the int operator, as determined by Eugene Fotin and expanded upon by Gene Belitski. The best workaround is to use System.Int32.MaxValue or a unique type alias as described below.


Consider the following record type:

type User = {
    Username : string
}

I want Username to be at least three characters long, so I use the StringLength attribute. There is no maximum length, so I set it to int.MaxValue:

type User = {
    [<StringLength(int.MaxValue, MinimumLength=3)>]
    Username : string
}

This gives me the following error:

This is not a valid constant expression or custom attribute value.

Everything is peachy if I use System.Int32 instead:

type User = {
    [<StringLength(System.Int32.MaxValue, MinimumLength=3)>]
    Username : string
}

It also compiles if I alias int:

type User = {
    [<StringLength(num.MaxValue, MinimumLength=3)>]
    Username : string
}
and num = int

Or fully qualify the type:

type User = {
    [<StringLength(Microsoft.FSharp.Core.int.MaxValue, MinimumLength=3)>]
    Username : string
}

I checked in the F# source and int is defined exactly as you would expect:

type int32 = System.Int32
// Then, a few lines later…
type int = int32

What's going on? I assumed that F# primitive types were interchangeable with other types in most contexts, but it looks like something is missing from my mental model.

like image 843
Jordan Gray Avatar asked Nov 28 '13 16:11

Jordan Gray


1 Answers

That's how F# type inference works in different contexts with different syntactic entities coincidentally having the same name, which in case of int may be any of:

  • function int:'T->int of full name Microsoft.FSharp.Core.Operators.int
  • type int = int32 of full name Microsoft.FSharp.Core.int
  • type int<'Measure> = int of full name Microsoft.FSharp.Core.int<_>

One way to demo this workings would be the following scenario: if we just enter

int;;

in FSI we'll get something like

val it : (int -> int) = <fun:it@3>

in other words, it's a function that cannot have MaxValue property associated with it:

> int.MaxValue;;

int.MaxValue;;
----^^^^^^^^

... error FS0039: The field, constructor or member 'MaxValue' is not defined

The same would apply to int32, which, when being used in a context of expression is inferred by FSI as just another function with signature (int -> int32).

Now when it comes to

type num = int

in this context int is inferred to be a type name abbreviation for System.Int32, so num is a type abbreviation as well, but now name ambiguity does not have place, so num.MaxValue is inferred exactly what we expect it to be, giving in FSI

> num.MaxValue;;
val it : int = 2147483647

Finally, when you use Microsoft.FSharp.Core.int you explicitly refer to the type entity, there is no place for ambiguity, so it works as expected.

Back to your use case with attribute parameter - in this context int is treated by type inference as part of expression to deliver argument value, i.e. as function, unless you explicitly or indirectly set another interpretation.

like image 157
Gene Belitski Avatar answered Nov 15 '22 21:11

Gene Belitski