#f#
#f#
Вопрос:
Сегодня я заметил, что следующее не компилируется:
open System
type MyType() =
member this.Something() =
this.F(3)
this.F("boo")
// ^^^^^
// This expression was expected to have type 'int' but here has type 'string'
member private this.F<'T> (t:'T) =
// ^^
// This type parameter has been used in a way that constrains it to always be 'int'
// This code is less generic than required by its annotations because the explicit type variable 'T' could not be generalized. It was constrained to be 'int'.
Console.WriteLine (t.GetType())
Но просто измените порядок объявления, и проблем не будет.
open System
type MyType() =
member private this.F<'T> (t:'T) =
Console.WriteLine (t.GetType())
member this.Something() =
this.F(3)
this.F("boo")
Мне потребовалось много времени, чтобы разобраться, поскольку я не ожидал, что порядок объявления будет иметь значение для членов класса. Это ожидаемое поведение?
Ответ №1:
Это тонкий побочный эффект того, как работает вывод типа F #. Я не думаю, что есть лучшее решение этой проблемы, чем изменение порядка ваших определений.
Чтобы предоставить больше информации, члены класса автоматически рассматриваются как взаимно рекурсивные (это означает, что они могут вызывать друг друга). В отличие от нескольких типов или нескольких функций в модуле, вам не нужно объявлять это явно, используя rec
ключевое слово.
Однако проблема не ограничивается классами. Вы можете получить точно такое же поведение с помощью простых функций. Самым простым возможным примером является:
let rec test() =
f 3 // Warning: causes code to be less generic
f "boo" // Error: string does not match int
and f (arg:'a) = ()
В обратном порядке это работает просто отлично:
let rec f (arg:'a) = ()
and test() =
f 3
f "boo"
Проблема в том, что средство проверки типов анализирует код сверху вниз. В первом случае это:
- Видит
f 3
и делает вывод, чтоf
имеет типint -> unit
- Видит
f "boo"
и сообщает об ошибке типа - Видит
f (arg:'a)
и понимает, что преждевременно использовал более конкретный тип, чем необходимо (и сообщает о различных предупреждениях).
Во втором случае это:
- Видит
f (arg:'a)
и делает вывод, чтоf
имеет тип'a -> unit
- Видит
f 3
иf "boo"
и использует создание экземпляра соответствующего типа
Основная причина, по которой средство проверки типов не может быть более умным, заключается в том, что средство проверки типов выполняет «обобщение» (т. Е. Определяет, что функция является универсальной) только после анализа всего тела функции (или всего рекурсивного блока). Если он находит более конкретное применение внутри тела, он никогда не дойдет до этого этапа обобщения.
Комментарии:
1. Я понимаю. Что ж, по крайней мере, она последовательна и не ограничивается занятиями. Я полагаю, это не считалось бы ошибкой, хотя для меня это определенно было неожиданностью.
Ответ №2:
Точно так же, как порядок файлов имеет значение в F #, порядок строк тоже имеет значение. В общем, ничто из того, что объявлено позже в файле, не доступно для выражений ранее в этом файле. Требуется некоторое время, чтобы привыкнуть, но в конечном итоге это отличный способ предотвратить случайное написание спагетти-кода. Есть некоторые исключения:
но я не думаю, что существует какой-либо механизм, позволяющий помещать общее объявление позже в файл и, вероятно, просто что-то, что вам нужно знать о том, как работает F #.
Комментарии:
1. Просто кажется странным, что это будет работать для не общих членов, но вызовет проблему с ограничением типа для общих. Я ищу вескую причину такого поведения; если я не смогу ее найти, я открою проблему на Github.
2. Потому что тип выводится на основе первого использования, если он явно не объявлен универсальным.