Combination of special OCaml tag for printf and value polymorphism.
As you probably know, Printf.printf does not accept string , but the data type is format . The OCaml type checker has a special rule for entering string literals for printf : if it is printed as format , if the context asks:
# "%d";; - : string = "%d"
A system like OCaml has another trick called value polymorphism (more precisely, it is a softened value polymorphism). Its purpose is to correctly type expressions with side effects. I do not explain its details, but it limits polymorphism: some forms of expressions called "expansive" cannot have polymorphic types:
In the above example (fun x -> x) (fun x -> x) does not have a polymorphic type, and the identity function fun x -> x has. This is due to the form of the expression (fun x -> x) (fun x -> x) : it is "expansive." A strange variable of type '_a is a variable of monomorphic type: it can be instantiated by a certain type only once. On the other hand, polymorphic variables of type 'a can be created for a different type for each vlaue use.
Back to your code:
Here p has a polymorphic type ('a, out_channel, unit) format -> 'a . 'a can be created for more than one type, therefore p "abc"; p "%d" 3 p "abc"; p "%d" 3 is typical: a polymorphic type can be created for (unit, out_channel, unit) format -> unit for the first use p and (int -> unit, out_channel, unit) format -> int -> unit for the second use p .
As soon as you change the constant 2 to 2+2 , which is expansive, the whole expression also becomes expansive, and the text input changes:
Here p no longer has the polymorphic variables 'a , but is monomorphic '_a . This monomorphic variable is unified (created) to unit first time p used, and as a result, the type p becomes (unit, out_channel, unit) format -> unit . It can take only 1 argument, so entering the second use of p with 2 arguments is not performed.
One easy way to avoid this situation is to split your definition into two:
let a = 2 + 2 in let p = Printf.printf in p "abc"; p "%d" 3