Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

OCaml  Module 2 – Expressions, values and functions

Type inference, polymorphism and type errors

Read the types OCaml infers, including 'a and '_weak1, see why let-bound functions work at many types, and trace a type error back to its real cause.

  • Beginner
  • 10 minutes
  • Examples run with ocaml 5.5.1, opam 2.6.0, dune 3.24.2 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Read inferred types, including type variables such as 'a and 'b
  • Explain let-polymorphism and why let pair = (id 1, id "one") type-checks
  • Recognise weak type variables caused by the value restriction, and fix them with a parameter or an annotation
  • Locate the real cause of a type error from the expected and actual types in the message

Before you start

What you’ll learn · Before you start

What you will learn

  • Read inferred types, including type variables such as 'a and 'b
  • Explain let-polymorphism and why let pair = (id 1, id "one") type-checks
  • Recognise weak type variables caused by the value restriction, and fix them with a parameter or an annotation
  • Locate the real cause of a type error from the expected and actual types in the message

Before you start

On this page

let count = List.fold_left (fun acc _ -> acc + 1) 0 looks harmless, yet the toplevel gives it the odd type '_weak1 list -> int, and the first list you count decides forever what it may count. Every type in that answer was worked out by OCaml itself, from how the values are used.

Here you read the types the compiler infers, see why a function bound with let can be used at many types while a parameter cannot, recognise weak type variables and remove them, and trace an error message back to the line that is really wrong.

OCaml works out every type from how the values are used

You rarely write a type in OCaml. The compiler starts each name with an unknown type and collects facts as it reads the body: a value passed to + must be an int, a value applied to an argument must be a function, two branches of an if must agree. This is type inference: Working out the types of values and functions from how they are used, so most types need not be written; OCaml prints them in the toplevel. More in “What OCaml is and where it is used”, and it ends either with a type for every expression or with an error at the first place where two facts disagree. When nothing forces a type, it stays a type variable: A placeholder such as 'a in an inferred type, standing for any type; every occurrence of the same letter is the same type., written with a quote: 'a, 'b, read “alpha” and “beta”. A type variable means “any type, the same one wherever this letter appears”.

The session below defines a few small functions and prints what OCaml inferred for each:

The types the toplevel inferred Python · infer_session.py
from session import toplevel

# The toplevel prints the type it inferred for every definition. Type variables such as 'a stand for "any type", and a
# let-bound polymorphic value can be used at two types in one expression, while a function parameter cannot unless
# its annotation makes it polymorphic, which OCaml 5.5 allows.
toplevel("""
let id x = x;;
let pair = (id 1, id "one");;
let pair_of f = (f 1, f "one");;
let both = let id2 x = x in (id2 1, id2 "one");;
let pair_of (f : 'a. 'a -> 'a) = (f 1, f "one");;
pair_of Fun.id;;
let twice f x = f (f x);;
twice (fun n -> n * 3) 2;;
let compose f g x = f (g x);;
let swap (a, b) = (b, a);;
let first_or default l = match l with [] -> default | x :: _ -> x;;
List.map;;
fun x -> x +. 1.;;
let count = List.fold_left (fun acc _ -> acc + 1) 0;;
count [10; 20; 30];;
count ["a"];;
let count_all l = List.fold_left (fun acc _ -> acc + 1) 0 l;;
""")

Output

$ ocaml
OCaml version 5.5.1
Enter #help;; for help.

# let id x = x;;
val id : 'a -> 'a = <fun>
# let pair = (id 1, id "one");;
val pair : int * string = (1, "one")
# let pair_of f = (f 1, f "one");;
Line 1, characters 24-29:
1 | let pair_of f = (f 1, f "one");;
                            ^^^^^
Error: This constant has type string but an expression was expected of type
         int
# let both = let id2 x = x in (id2 1, id2 "one");;
val both : int * string = (1, "one")
# let pair_of (f : 'a. 'a -> 'a) = (f 1, f "one");;
val pair_of : ('a. 'a -> 'a) -> int * string = <fun>
# pair_of Fun.id;;
- : int * string = (1, "one")
# let twice f x = f (f x);;
val twice : ('a -> 'a) -> 'a -> 'a = <fun>
# twice (fun n -> n * 3) 2;;
- : int = 18
# let compose f g x = f (g x);;
val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
# let swap (a, b) = (b, a);;
val swap : 'a * 'b -> 'b * 'a = <fun>
# let first_or default l = match l with [] -> default | x :: _ -> x;;
val first_or : 'a -> 'a list -> 'a = <fun>
# List.map;;
- : ('a -> 'b) -> 'a list -> 'b list = <fun>
# fun x -> x +. 1.;;
- : float -> float = <fun>
# let count = List.fold_left (fun acc _ -> acc + 1) 0;;
val count : '_weak1 list -> int = <fun>
# count [10; 20; 30];;
- : int = 3
# count ["a"];;
Line 1, characters 7-10:
1 | count ["a"];;
           ^^^
Error: This constant has type string but an expression was expected of type
         int
# let count_all l = List.fold_left (fun acc _ -> acc + 1) 0 l;;
val count_all : 'a list -> int = <fun>

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 infer_session.py

let id x = x got 'a -> 'a: whatever goes in comes out, so the argument and the result share one variable. swap got 'a * 'b -> 'b * 'a, two variables because the two parts of the pair may differ. List.map itself has the type ('a -> 'b) -> 'a list -> 'b list, which says everything about it except its name: give it a function from 'a to 'b and a list of 'a, and it returns a list of 'b. fun x -> x +. 1. got float -> float, because +. adds floats only.

Step through how the facts fix the type of twice, whose body applies f two times:

Interactive · step by step How OCaml works out the type of twice Step through the body of twice and watch each use of f add a fact about the types, until one type is left.
How OCaml works out the type of twice, step 5 of 5. Showing x ('a), f ('a -> 'a), f x ('a), f (f x) ('a), twice (('a -> 'a) -> 'a -> 'a). In focus: twice.argumentargumentx'af'a -> 'af x'af (f x)'atwice('a -> 'a) -> 'a -> 'a

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Nothing known yet. OCaml reads let twice f x = f (f x) without any types written. It gives x an unknown type, written 'a, and looks at how f is used.
  2. f is applied to x. f x applies f to x, so f must be a function whose argument has the type of x. Its result is still unknown: call it 'b.
  3. f is applied again. f (f x) gives f the value f x, of type 'b. A function has one argument type, so 'b and 'a must be the same type.
  4. The result. The body's value is f (f x), which now has type 'a. No use forces 'a to be int, string or anything else, so it stays a variable.
  5. Generalised. twice gets the type ('a -> 'a) -> 'a -> 'a, and each use chooses its own 'a: twice (fun n -> n * 3) 2 chose int and gave 18.

The second application is what ties the result type to the argument type. A function applied to its own result must return the type it takes, which is why twice wants a function of type 'a -> 'a and why twice (fun n -> n * 3) 2 gave 18.

Ask the toplevel

When a definition does not do what you expect, type its name alone, such as twice;;, and read the type. A type variable where you expected int, or int where you expected 'a, usually points straight at the mistake.

A function bound with let works at many types, a parameter at only one

let pair = (id 1, id "one") type-checked and gave (1, "one"): one expression used id at int and at string. That works because a let binding is generalisation: Turning the type variables of a let-bound value into ones each use may choose afresh, which lets id work at int and at string.. Once id is defined, its 'a may stand for a different type at each use, as if every use received a fresh copy of the type. This is called let-polymorphism, and the core-language chapter of the manual relies on it from its first examples.

A function’s parameter is not generalised. let pair_of f = (f 1, f "one") was refused: the toplevel underlined "one", a string, at a place where only an int could go. Inside the body f has one type, and f 1 had already fixed its argument to int when f "one" came along.

OCaml 5.5: a parameter may be declared polymorphic

OCaml 5.5 lets an annotation ask for a polymorphic parameter: let pair_of (f : 'a. 'a -> 'a) = (f 1, f "one") type-checks, and pair_of Fun.id gave (1, "one"). Without the 'a. the annotation’s 'a is still one type.

Without such an annotation, the rule is about where a name is bound, not about what it is:

How the name is bound Example from the session Different types in one body?
A top-level let let id x = x, then (id 1, id "one") Yes: (1, "one")
A let … in inside an expression let id2 x = x in (id2 1, id2 "one") Yes: (1, "one")
A function parameter let pair_of f = (f 1, f "one") No: a type error

Notice where the error was reported: at "one", the second use, although f 1 is written first and either use alone is fine. A type error marks the point where the checker met a contradiction, which is the subject of the last section.

Key idea

A let-bound value is generalised, so each use may pick its own type for ’a; a parameter keeps one type throughout the body.

Checkpoint

Check: Reading inferred types · let-polymorphism

  1. Question 1 of 2 What type does OCaml infer for let apply f x = f x?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: ('a -> 'b) -> 'a -> 'b

    Each use adds a fact: f x needs f : 'a -> 'b when x : 'a. Nothing more is known, so both variables stay, as List.map's type kept 'a and 'b.

  2. Question 2 of 2 Which of these type-checks?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: let id x = x in (id 1, id true)

    Generalisation happens at let, not at a parameter. pair_of failed for the same reason the second and third choices fail.

A weak type variable is one unknown type that the first use fixes

Some definitions get a type variable with an underscore, '_weak1. The manual calls these weakly polymorphic type variables: each one is a placeholder for a single type that is not known yet, not for any type. They come from the value restriction: The rule that OCaml generalises a let only when its right-hand side is a syntactic value: the result of a call such as ref [] keeps weak type variables such as '_weak1, which its first use fixes. A relaxed form still generalises a variable that appears only in covariant positions.. A definition whose right-hand side is the result of a function call, such as ref [] or List.map (fun x -> x), might hide mutable state, so OCaml does not generalise its type variables. The session shows two of them and what the first use does to each:

Weak type variables, fixed by the first use Python · weak_session.py
from session import toplevel

# The value restriction: a definition that is the result of a computation, not a function written with fun, gets weak
# type variables, '_weak1 and so on, but only where a type variable is left. The first use fixes them; writing the
# parameter out keeps the type polymorphic.
toplevel("""
let r = ref [];;
r := [3];;
r;;
let f = List.map (fun x -> x);;
f [1; 2];;
f;;
f ["a"];;
let lengths = List.map String.length;;
let g l = List.map (fun x -> x) l;;
g [1; 2];;
g ["a"];;
""")

Output

$ ocaml
OCaml version 5.5.1
Enter #help;; for help.

# let r = ref [];;
val r : '_weak1 list ref = {contents = []}
# r := [3];;
- : unit = ()
# r;;
- : int list ref = {contents = [3]}
# let f = List.map (fun x -> x);;
val f : '_weak2 list -> '_weak2 list = <fun>
# f [1; 2];;
- : int list = [1; 2]
# f;;
- : int list -> int list = <fun>
# f ["a"];;
Line 1, characters 3-6:
1 | f ["a"];;
       ^^^
Error: This constant has type string but an expression was expected of type
         int
# let lengths = List.map String.length;;
val lengths : string list -> int list = <fun>
# let g l = List.map (fun x -> x) l;;
val g : 'a list -> 'a list = <fun>
# g [1; 2];;
- : int list = [1; 2]
# g ["a"];;
- : string list = ["a"]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 weak_session.py

let r = ref [] got '_weak1 list ref. After r := [3], r had type int list ref, and it keeps that type: if it were 'a list ref, the program could store ints and then read them back as strings. let f = List.map (fun x -> x) got '_weak2 list -> '_weak2 list; f [1; 2] fixed it to int list -> int list, and the next call, f ["a"], was refused. Only type variables are weakened: let lengths = List.map String.length is a partial application too, but it got the complete type string list -> int list. Writing the parameter out, let g l = List.map (fun x -> x) l, turns the definition into a function written with a parameter, which is generalised again: g took both [1; 2] and ["a"]. Adding the parameter is called eta-expansion. Step through the same definitions:

Interactive · step by step A weak type variable is decided by the first use Step through the session and watch each weak variable turn into one fixed type, while g stays polymorphic.
A weak type variable is decided by the first use, step 5 of 5. Showing r = ref [] (int list ref), f = List.map (fun …) (int list -> int list), r := [3], f [1; 2], g l = List.map … l ('a list -> 'a list). In focus: g l = List.map … l.fixesfixesr = ref []int list reff = List.map (fun …)int list -> int listr := [3]f [1; 2]g l = List.map … l'a list -> 'a list

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. An empty ref. let r = ref [] is the result of a call, ref [], not a function written with fun. Its type is '_weak1 list ref: one list type, not known yet.
  2. The first use decides. r := [3] stores a list of ints, so '_weak1 becomes int. From now on r has type int list ref and can never hold strings.
  3. A partial application. let f = List.map (fun x -> x) is also the result of a call, so it gets a weak type too: '_weak2 list -> '_weak2 list.
  4. f is fixed to int. f [1; 2] gave [1; 2] and fixed the type to int list -> int list. The next phrase, f ["a"], failed: a string is not an int.
  5. Write the parameter out. let g l = List.map (fun x -> x) l is a function written with a parameter, so it is generalised: 'a list -> 'a list, and it took both ints and strings.

The count of the opening, at the end of the first session, is the same case. List.fold_left (fun acc _ -> acc + 1) 0 is a partial application, so it got '_weak1 list -> int; count [10; 20; 30] gave 3 and fixed the type to ints, and count ["a"] was refused. Written with its parameter, let count_all l = … l got 'a list -> int.

In the toplevel a weak type can wait for its first use. In a compiled file it cannot, because every module must have a complete type when it is compiled:

A weak type stops ocamlc, an annotation fixes it Python · compile_weak_session.py
from session import run, write

# The toplevel lets a weak type wait for its first use, but a compiled module must have a complete type. ocamlc
# refuses a module whose top-level value still has a weak type; an annotation gives it one.
write("store.ml", "let store = ref []\n")
write("store_fixed.ml", "let store : string list ref = ref []\n")
run("""
cat store.ml
!ocamlc -c store.ml
cat store_fixed.ml
ocamlc -c store_fixed.ml
ocamlc -i store_fixed.ml
""")

Output

$ cat store.ml
let store = ref []
$ ocamlc -c store.ml
File "store.ml", line 1, characters 4-9:
1 | let store = ref []
        ^^^^^
Error: The type of this expression, '_weak1 list ref,
       contains the non-generalizable type variable(s): '_weak1.
       (see manual section 6.1.2)
[exit status 2]
$ cat store_fixed.ml
let store : string list ref = ref []
$ ocamlc -c store_fixed.ml
$ ocamlc -i store_fixed.ml
val store : string list ref

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 compile_weak_session.py

ocamlc -c store.ml stopped with “contains the non-generalizable type variable(s): ’_weak1”, and the annotated let store : string list ref = ref [] compiled, with the type val store : string list ref that ocamlc -i printed.

Removing a weak type with an unrelated first use

Wrong: calling f [1; 2] once at the top of a file “to make the type work”, which silently fixes f to ints for the whole program. Right: write the parameter out when the function should stay general, or annotate the type you mean, as store did.

A type error marks where two types met, not always the mistake

An error message names a place and two types: the type the expression has, and the type the context expected. The place is where the checker found the contradiction, which can be after the line you got wrong. Four broken definitions and their messages:

Four type errors and where they are reported Python · errors_session.py
from session import toplevel

# Four broken definitions and the messages OCaml 5.5.1 gives for them. The last two are the same mistake: a sum that
# starts from the int 0 but adds floats. Without annotations the error appears at a later use; with them, at the 0.
toplevel("""
let add_tip bill = bill + 1.5;;
"Total: " ^ 42;;
String.length ['a'; 'b'];;
let rec sum l = match l with [] -> 0 | x :: rest -> x +. sum rest;;
let rec sum (l : float list) : float = match l with [] -> 0 | x :: rest -> x +. sum rest;;
let rec sum (l : float list) : float = match l with [] -> 0. | x :: rest -> x +. sum rest;;
sum [1.5; 2.25];;
""")

Output

$ ocaml
OCaml version 5.5.1
Enter #help;; for help.

# let add_tip bill = bill + 1.5;;
Line 1, characters 26-29:
1 | let add_tip bill = bill + 1.5;;
                              ^^^
Error: The constant 1.5 has type float but an expression was expected of type
         int
# "Total: " ^ 42;;
Line 1, characters 12-14:
1 | "Total: " ^ 42;;
                ^^
Error: The constant 42 has type int but an expression was expected of type
         string
# String.length ['a'; 'b'];;
Line 1, characters 14-24:
1 | String.length ['a'; 'b'];;
                  ^^^^^^^^^^
Error: This expression should not be a list literal, the expected type is
       string
# let rec sum l = match l with [] -> 0 | x :: rest -> x +. sum rest;;
Line 1, characters 57-65:
1 | let rec sum l = match l with [] -> 0 | x :: rest -> x +. sum rest;;
                                                             ^^^^^^^^
Error: This expression has type int but an expression was expected of type
         float
# let rec sum (l : float list) : float = match l with [] -> 0 | x :: rest -> x +. sum rest;;
Line 1, characters 58-59:
1 | let rec sum (l : float list) : float = match l with [] -> 0 | x :: rest -> x +. sum rest;;
                                                              ^
Error: The constant 0 has type int but an expression was expected of type
         float
Hint: Did you mean 0.?
# let rec sum (l : float list) : float = match l with [] -> 0. | x :: rest -> x +. sum rest;;
val sum : float list -> float = <fun>
# sum [1.5; 2.25];;
- : float = 3.75

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py

The first three point at the mistake itself. bill + 1.5 mixes an int operator with a float, "Total: " ^ 42 joins a number to a string, and String.length ['a'; 'b'] passes a list where a string belongs; OCaml 5.5.1 even says “This expression should not be a list literal”. The fourth is the instructive one. The sum starts with [] -> 0, which makes the result an int, and only later meets x +. sum rest, which needs a float. The message underlines sum rest, a correct expression, because that is where an int met a context that wanted a float:

Message points at The real cause Fix
1.5: a float where an int was expected + adds ints only bill +. 1.5, with a float bill
42: an int where a string was expected ^ joins strings string_of_int 42
['a'; 'b']: a list where a string was expected a list of chars is not a string "ab"
sum rest: an int where a float was expected the 0 of the first branch 0.

With the types written down, (l : float list) : float, the checker knew the result type before it read the branches, and the same mistake was reported at the 0, with the hint “Did you mean 0.?”. Annotations never change what a program does; they only give the checker a fact earlier, which moves the message closer to the mistake. The corrected sum [1.5; 2.25] gave 3.75.

Read the two types, then look earlier

When the underlined code looks right, ask where the “actual” type came from. Follow that value back to the line that first decided its type, or add an annotation on the function and compile again.

Checkpoint

Check: Weak type variables · Tracing a type error to its cause

  1. Question 1 of 2 let cache = ref [];; cache := ["x"];; What is the type of cache now?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: string list ref

    The session's r went from '_weak1 list ref to int list ref after r := [3] in the same way.

  2. Question 2 of 2 let rec total l = match l with [] -> 0 | x :: rest -> x +. total rest gives an error underlining total rest. What is the real mistake?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The 0 of the first branch, which makes the result an int; it should be 0.

    The message is reported where two types met. Annotating (l : float list) : float moved it to the 0, with the hint "Did you mean 0.?".

Practise: reading types and errors

The questions below give definitions like the ones in the sessions. Decide the type or the error from the rules, then check your answer against the toplevel’s output:

Practice that adapts to you

Practise this lesson

8 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 In the type 'a -> 'a, what does 'a mean?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: Any type, the same one in both places

    id has type 'a -> 'a: whatever type goes in comes out.

  2. Question 2 of 8 let swap (a, b) = (b, a) was inferred as which type?

    Choose one answer.

    Show the answer: Question 2 of 8

    Answer: 'a * 'b -> 'b * 'a

    The session printed val swap : 'a * 'b -> 'b * 'a.

  3. Question 3 of 8 Why may one expression use id at int and at string?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: id was bound with let, so its type variable is generalised and each use gets its own copy

    A let binding's type variables are generalised once the definition is complete.

  4. Question 4 of 8 You need a helper that applies a given function to an int and to a string. Which works?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: Take two functions as parameters, one for each type, or define the function with let and use it directly

    Generalisation happens at let. A plain annotation keeps one type; only the explicitly polymorphic annotation of OCaml 5.5, (f : 'a. 'a -> 'a), makes a parameter work at two types, as the session's second pair_of did.

  5. Question 5 of 8 What does '_weak1 in a type mean?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: One particular type that is not known yet, fixed by the first use

    Weak variables come from the value restriction, which keeps the results of function calls from being generalised.

  6. Question 6 of 8 Does let lengths = List.map String.length get a weak type, like count did?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: No; its type is string list -> int list, with no type variable to weaken

    The value restriction only affects type variables. count had one ('_weak1 for the list's elements); lengths has none, because every type is forced.

  7. Question 7 of 8 What does adding a type annotation such as (l : float list) change?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: Only what the checker knows and where it reports errors; the program runs the same

    With the annotations, the sum's error moved from sum rest to the 0, where the mistake was.

  8. Question 8 of 8 "Total: " ^ 42 gives "The constant 42 has type int but an expression was expected of type string". Which fix is right?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: "Total: " ^ string_of_int 42

    The message's expected type, string, says what the context wants; its actual type, int, says what was given.

Interview questions

Warm-up (fresher to 2–5 years): what does a type such as ’a -> ’a mean, and who writes it?

OCaml writes it. The compiler infers the type of every definition from how its values are used, and when nothing forces a particular type it leaves a type variable such as 'a. 'a -> 'a is the type of a function that returns a value of the same type it receives, whatever that type is: let id x = x has it. Because a let binding is generalised, each use of id can pick its own type, so (id 1, id "one") is fine. A variable with an underscore, '_weak1, is different: it stands for one type not known yet, which the first use will fix.

Two harder questions follow, about a module that does not compile because of a weak type and about an error message that underlines correct code. Decide your answer, then open each first step:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A file defines let cache = ref [] at the top level. The same line worked in the toplevel, but ocamlc now refuses the file with an error about non-generalizable type variables. Explain the error and two ways to fix it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A colleague's build fails with "This expression has type int but an expression was expected of type float", underlining a recursive call that looks right. How would you find the real mistake, and how could the code keep such errors closer to their cause?

    Model answer, Pro. First step and follow-ups

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Where do the types the toplevel prints come from?

    OCaml infers each one from how the values are used, and leaves a type variable such as 'a where nothing forces a particular type.

  2. What does the type 'a -> 'a describe?

    A function that returns a value of the same type it receives, whatever that type is, as let id x = x does.

  3. Why can (id 1, id "one") type-check when a parameter cannot be used at two types?

    A let binding is generalised, so each use of id picks its own type for 'a; a function parameter keeps one type throughout the body.

  4. What is a weak type variable such as '_weak1, and how is it removed?

    A single unknown type left by the value restriction, which the first use fixes for good. Write the parameter out, or annotate the type you mean.

  5. What does OCaml 5.5 accept in let pair_of (f : 'a. 'a -> 'a) = (f 1, f "one")?

    A parameter declared polymorphic: pair_of Fun.id gave (1, "one"). Without the 'a., the annotation's 'a stands for one type.

  6. Why can a type error point at a line that looks correct?

    It marks where two types met, which may come after the real mistake. Trace the "actual" type back to where it was decided, or annotate the function so the message moves to the cause.

Key takeaways

  • OCaml infers every type from how values are used, and leaves a type variable such as 'a where nothing forces a particular type.
  • A let binding is generalised, so id can be used at int and string in one expression, while a function parameter has one type throughout its body.
  • A weak type variable such as '_weak1 is a single unknown type caused by the value restriction; the first use fixes it, and a written-out parameter or an annotation removes it.
  • A type error marks where two types met, which may be after the real mistake; annotations give the checker the facts earlier and move the message to the cause.

Terms in this lesson

generalisation
Turning the type variables of a let-bound value into ones each use may choose afresh, which lets id work at int and at string.
type variable
A placeholder such as 'a in an inferred type, standing for any type; every occurrence of the same letter is the same type.
value restriction
The rule that OCaml generalises a let only when its right-hand side is a syntactic value: the result of a call such as ref [] keeps weak type variables such as '_weak1, which its first use fixes. A relaxed form still generalises a variable that appears only in covariant positions.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.