How come argument type cannot be infered in nested function?

How come this compiles:

type at = { x: int; }
type bt = { x: int; }

let f (b : bt) : int =
  let f' b = b.x in f' b

But not when defining bt first:

type bt = { x: int; }
type at = { x: int; }

let f (b : bt) : int =
  let f' b = b.x in f' b
5 |   let f' b = b.x in f' b
                           ^
Error: This expression has type bt but an expression was expected of type at

If I explicitly tell the type of b in f' it works:

type bt = { x: int; }
type at = { x: int; }

let f (b : bt) : int =
  let f' (b : bt) = b.x in f' b

Shouldn’t the type checker be able to infer the type of b in f' just by seeing that we are passing b which is of type bt?

two things:
type inference happens at definition not application site
when ambiguous, the latest definition of a record/constructor is always taken

a different solution to annotating ambiguous records is defining them inline to a constructor

type bt = Bt of { x: int; }
type at = At of { x: int; }

let f b =
  let f' (Bt b) = b.x in f' b

this should have exactly the same memory layout as plain records.

I’m a bit surprised by this point. If we check the the documentation on the C interfacing, it requires an explicit anotation [@@unboxed] otherwise it cost an indirection.

Edit : got it, this is bit later in the documentation:

As an optimization, unboxable concrete data types are represented specially; a concrete data type is unboxable if it has exactly one constructor and this constructor has exactly one argument. Unboxable concrete data types are represented in the same ways as unboxable record types

If you want to be told when this sort of things happen (when a “last in scope” choice makes reordering types more fragile), you may want to enable warning 41 (ambiguous-name) by default:

$ ocamlc -w +ambiguous-name test.ml
File "test.ml", line 5, characters 15-16:
5 |   let f' b = b.x in f' b
                   ^
Warning 41 [ambiguous-name]: x belongs to several types: bt at
The first one was selected. Please disambiguate if this is wrong.
2 Likes

I feel this inference question is quite common and should be added to an FAQ, or at least to the ‘common errors’ page: Common Error Messages · OCaml Tutorials

3 Likes

No, that’s actually an example of inline records, not a standalone record type. You could have multiple constructors using inline records with multiple fields each and their representation would still be a single block for each.