liveBook · Manning

Type inference

As explained in F# in Action: A Revised Edition of Get Programming with F#

Type inference is the compile-time deduction of expression and value types from available program information.

Type inference lets the F# compiler determine symbol types from how values are defined and used, rather than requiring an annotation on every binding. F# remains statically typed: inferred code receives the same compiler checking while removing routine type declarations. section 3.2 section 3.2.1

Use it by default for values, functions, records, and composed operations. Less annotation makes code quicker to write and easier to read; when a refactor changes real type relationships, compiler errors identify the clashes instead of requiring boilerplate edits throughout the codebase. section 3.2.1 section 3.4

Inferred and explicit bindings

Both bindings represent an integer value; the annotation is optional when the right-hand side already establishes the type. An incompatible annotation produces a compiler error. let i = 10 let i : int = 10 section 3.2.2

Inference follows program constraints rather than guessing. If an unexpected early use makes a parameter appear to be a string, later numeric call sites can fail. Inspect the inferred signature, then add a temporary annotation at the intended boundary to move the error to the underlying mismatch. section 3.2.4 section 3.2.6

let calculateGroup age =                              #1
    if age < 18 then "Child"
    elif age < 65 then "Adult"
    else "Pensioner"
 
let sayHello someValue =                              #2
    let group =                                       #3
        if someValue < 10.0 then calculateGroup 15
        else calculateGroup 35
    "Hello " + group
 
let result = sayHello 10.5                            #4

Experiment with the age comparison: replacing 18 with "test" changes the inferred type of age and exposes errors at dependent call sites. section 3.2.4

Annotations still matter where inference lacks enough information. In particular, member access on classes or C# framework types may need an explicit parameter type; once a type is known, inference can continue downstream. Records and pattern matching often supply enough field or pattern information for the compiler to infer their types without annotations. section 3.2.5 section 5.3.2 section 8.1.5

Functions establish contracts through use

A higher-order function can infer that printer accepts a string and returns unit because it is called with strings. Any supplied function with that signature can then serve as the printer, including a partially applied file-writing function. section 15.2.2

Use IDE type displays such as CodeLens or LineLens to see inferred types as code changes. Prefer meaningful names over annotations solely meant to explain intent, and introduce annotations when they clarify an ambiguity or guide the compiler. section 3.2.2 section 3.2.6

How F# in Action: A Revised Edition of Get Programming with F# covers it

How deep it goes
Explains inference through bindings, functions, records, diagnostics, limitations, and IDE inspection; defers object-oriented details.
Examples
Uses integer bindings, an age-classification error exercise, record construction, pattern matching, and pluggable customer printers.
What it assumes
Builds from basic F# syntax and value bindings without assuming prior F# or .NET knowledge.
What you can do after
Diagnose/debug with compiler type displays and targeted annotations when inferred types create unexpected errors.
How it connects
Connects type inference with type annotations, records, pattern matching, higher-order functions, function composition, and refactoring.

Also explained in

Compare how 16 books explain type inference →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of F# in Action: A Revised Edition of Get Programming with F#. The section links point to the parts of the book it draws on.