Вывод типов: статика без аннотаций

F# статически типизирован, но типы почти всегда выводятся сами — компилятор работает за вас.

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

Типобезопасность без многословности

В C# или Java тип часто пишут руками (int x = 5;). F# наследует от ML мощный вывод типов (алгоритм Хиндли–Милнера): компилятор сам выясняет типы, оставляя код кратким, но при этом полностью статически типизированным.

let add a b = a + b   // выведен тип: int -> int -> int
let result = add 2 3
printfn "%d" result

Вывод:

5

Мы нигде не написали int, но компилятор понял: раз a + b используется с целыми при вызове, значит add работает с int. Передадим строку — будет ошибка на этапе компиляции, а не в рантайме.

Вывод идёт по всей программе

Вывод типов в F# учитывает использование значения дальше по коду, а не только строку объявления. Это отличает его от «локального» вывода var в C#.

let describe x =
    if x > 10 then "много" else "мало"
// x выведен как int (из-за x > 10), результат — string

Когда тип указывают явно

Иногда вывода недостаточно или мы хотим документировать намерение. Тогда добавляют аннотацию.

let toUpper (s: string) = s.ToUpper()
// без аннотации компилятор не знал бы, что s — строка с методом ToUpper

Аннотация нужна, когда тип нельзя вывести из контекста — например, при вызове перегруженного метода .NET или метода объекта.

Обобщённость по умолчанию

Если тип не привязан к конкретному, F# делает функцию обобщённой (generic) автоматически.

let identity x = x
// тип: 'a -> 'a   — работает для любого типа

Запись 'a — это типовая переменная (как T в C#). Здесь обобщённость возникла сама, без слова generic.

Как работает под капотом

Компилятор строит систему уравнений на типы: каждое использование значения добавляет ограничение, затем уравнения решаются унификацией. Если решение единственное — тип выведен; если значение используется одинаково обобщённо — выводится типовая переменная 'a; если ограничения противоречивы — ошибка типов. Всё это происходит до запуска, поэтому рантайм-проверок типов нет.

Частые ошибки

  • Думать, что отсутствие аннотаций = динамическая типизация. Это статика, просто типы выводятся.
  • Удивляться ошибке «не удалось вывести тип» при вызове метода .NET-объекта — добавьте аннотацию аргумента.
  • Ожидать вывод как в C# (по одной строке) — в F# вывод смотрит на всё использование.

Итоги

  • F# статически типизирован, но типы почти всегда выводятся автоматически (Хиндли–Милнер).
  • Ошибки типов ловятся на компиляции, не в рантайме.
  • Аннотация (x: тип) нужна, когда контекста для вывода мало.
  • Невязанные типы становятся обобщёнными ('a) автоматически.
Проверьте себя
1. Что значит, что F# выводит типы?
AТипы проверяются только в рантайме
BКомпилятор сам определяет типы по использованию, оставаясь статически типизированным
CТипы не проверяются вовсе
DВсе значения имеют тип object
2. Когда в F# обычно нужна явная аннотация типа?
AВсегда
BНикогда
CКогда тип нельзя вывести из контекста, например при вызове метода .NET-объекта
DТолько для чисел
3. Что означает тип 'a -> 'a?
AОшибку типов
BОбобщённую функцию: принимает значение любого типа и возвращает того же типа
CФункцию только для строк
DДинамический тип