Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Line of OCaml produces mysterious error

When I execute the code

let (a,p) = (2+2, Printf.printf) in p "abc"; p "%d" 3 ;;

I expect to see the output abc3, but get instead

File "f.ml", line 1, characters 46-47:
Error: This function has type (unit, out_channel, unit) format -> unit
       It is applied to too many arguments; maybe you forgot a `;'.

The fun part is that if I change 2+2 to 2, it runs.

Why does the code produce an error as is, but not with +2 removed?

like image 981
feersum Avatar asked Sep 29 '15 08:09

feersum


People also ask

How do I return something to OCaml?

OCaml doesn't have a return keyword — the last expression in a function becomes the result of the function automatically.

What keyword is used to declare variables and functions in OCaml?

At its simplest, a variable is an identifier whose meaning is bound to a particular value. In OCaml these bindings are often introduced using the let keyword.

What does function mean in OCaml?

Function in ocaml is a expression. That means, a function is a value that represents the function. This is also called anonymous function, lambda. (* syntax for a function expression *) fun n -> n + 1;; (* applying a function to a value *) (fun n -> n + 1) 2;; (* ⇒ 3 *)


1 Answers

A combination of OCaml's special typing trick for printf and value polymorphism.

As you may probably know, Printf.printf does not take string but data type format. OCaml type checker has a special rule for typing of string literals for printf: if it is typed as format if the context requests:

# "%d";;
- : string = "%d"
# ("%d" : _format);;
- : (int -> 'a, 'b, 'a) format = ...

OCaml type system has another trick called value polymorphism (more precisely, it is relaxed value polymorphism). Its purpose is to type expressions with side effects correctly. I do not explain its details but it restricts polymorphism: some forms of expressions called "expansive" cannot have polymorphic types:

# fun x -> x;;
- : 'a -> 'a = <fun>
# (fun x -> x) (fun x -> x)
- : '_a -> '_a = <fun>

In the above, (fun x -> x) (fun x -> x) has no polymorphic type, while the identity function fun x -> x has. This is due to the shape of the expression of (fun x -> x) (fun x -> x): it is "expansive". The strange type variable '_a is monomorphic type variable: it can be instantiated to some type only once. On the other hand, polymorphic variables like 'a can be instantiated to a different type for each use of the vlaue.

Let's go back to your code:

# let (a, p) = (2, Printf.printf);;
val a : int
val p : ('a, out_channel, unit) format -> 'a

Here, p has a polymorphic type ('a, out_channel, unit) format -> 'a. 'a can be instantiated to more than one types therefore p "abc"; p "%d" 3 is typable: the polymorphic type can be instantiated to (unit, out_channel, unit) format -> unit for the first use of p, and (int -> unit, out_channel, unit) format -> int -> unit for the second use of p.

Once you change the constant 2 to 2+2, which is expansive, the entire expression becomes expansive too, and the typing changes:

# let (a, p) = (2+2, Printf.printf);;
val a : int
val p : ('_a, out_channel, unit) format -> '_a

Here, p has no longer polymorphic variables 'a but monomorphic '_a. This monomorphic variable is unified (instantiated) to unit at the first use of p, and as a result p's type becomes (unit, out_channel, unit) format -> unit. It can take only 1 argument therefore the typing of the second use of p with 2 arguments fails.

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
like image 155
camlspotter Avatar answered Sep 30 '22 05:09

camlspotter