F # int.MaxValue является "недействительным постоянным выражением", но System.Int32.MaxValue является?

TL; DR: компилятор F # интерпретирует int в этом контексте как int operator, как определяемый Евгением Фотиным и расширенный Джином Белицким. Лучшим обходным решением является использование System.Int32.MaxValue или уникальный псевдоним типа, как описано ниже.


Рассмотрим следующий тип записи:

type User = {
    Username : string
}

Я хочу, чтобы Username имел длину не менее трех символов, поэтому я использую атрибут StringLength. Максимальная длина отсутствует, поэтому я устанавливаю ее на int.MaxValue:

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

Это дает мне следующую ошибку:

Это не допустимое константное выражение или значение настраиваемого атрибута.

Все персик, если я использую System.Int32 вместо этого:

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

Он также компилируется, если я псевдоним int:

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

Или полностью квалифицируйте тип:

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

Я проверил в источнике F # и int определяется точно так, как вы ожидали:

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

Что происходит? Я предположил, что примитивные типы F # были взаимозаменяемы с другими типами в большинстве контекстов, но похоже, что что-то отсутствует в моей ментальной модели.

Ответ 1

То, как вывод типа F # работает в разных контекстах с разными синтаксическими объектами, совпадающими с тем же именем, которые в случае int могут быть любыми из:

  • Функция int:'T->int полного имени Microsoft.FSharp.Core.Operators.int
  • type int = int32 полного имени Microsoft.FSharp.Core.int
  • type int<'Measure> = int полного имени Microsoft.FSharp.Core.int<_>

Один из способов демонстрации этой работы - это следующий сценарий: если мы просто вводим

int;;

в FSI мы получим что-то вроде

val it : (int -> int) = <fun:[email protected]>

Другими словами, это функция, которая не может иметь связанное с ней свойство MaxValue:

> int.MaxValue;;

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

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

То же самое относится к int32, который, будучи использованным в контексте выражения, вызывается FSI как просто еще одна функция с сигнатурой (int -> int32).

Теперь, когда дело доходит до

type num = int

в этом контексте int вызывается как аббревиатура имени типа для System.Int32, поэтому num также является аббревиатурой типа, но теперь имя двусмысленности не имеет места, поэтому num.MaxValue выводится именно то, что мы ожидаем, что это будет, давая в FSI

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

Наконец, когда вы используете Microsoft.FSharp.Core.int, вы явно ссылаетесь на объект типа, нет места для двусмысленности, поэтому он работает как ожидалось.

Вернемся к вашему варианту использования с параметром атрибута - в этом контексте int обрабатывается путем вывода типа как части выражения для доставки значения аргумента, т.е. как функция, если вы явно или косвенно не устанавливаете другую интерпретацию.

Ответ 2

Похоже, что компилятор обрабатывает int в параметре атрибута как функцию преобразования int.