-1

I'm trying to write a version of Printf.printf that always appends a newline character after writing its formatted output. My first attempt was

# let say fmt = Printf.ksprintf print_endline fmt;;
val say : ('a, unit, string, unit) format4 -> 'a = <fun>

The type signature looks right and say works as expected. I noticed that fmt is listed twice, and thought that partial application could eliminate it. So I tried this instead:

# let say = Printf.ksprintf print_endline;;
val say : ('_weak1, unit, string, unit) format4 -> '_weak1 = <fun>

The function definition looks cleaner, but the type signature looks wrong and say no longer works as expected. For example, say doesn't type check if the format string needs a variable number of arguments: I get an error that say "is applied to too many arguments".

I can use the let say fmt = … implementation, but why doesn't partial application work?

mndrix
  • 3,131
  • 1
  • 30
  • 23

2 Answers2

3

OCaml's type-checker loses polymorphism during partial application. That is, when you partially apply a function, the resulting function is no longer polymorphic. That's why you see '_weak1 in the second type signature.

When you include the fmt argument, you help the type-checker recognize that polymorphism is still present.

This process is called "eta conversion." Removing your fmt argument is "eta reduction" and adding it back in is called "eta expansion." You may encounter that terminology when working with other functional programming languages.

mndrix
  • 3,131
  • 1
  • 30
  • 23
3

This is the value restriction at work: https://ocaml.org/manual/polymorphism.html#s:weak-polymorphism . In brief, only syntactic values can be safely generalized in let-binding in presence of mutable variables in the language. In particular,

let f = fun x -> g y x

is a syntactic value that can be generalized, whereas

let f = g y

is a computation that cannot (always) be generalized.

A example works quite well to illustrate the issue, consider:

let fake_pair x =
  let store = ref None in
   fun y ->
     match !store with
     | None -> 
       store := Some y;
       x, y
     | Some s ->
       x, s 

then the type of fake_pair is 'a -> 'b -> 'a * 'b. However, once partially applied

let p = fake_pair 0

we have initialized the store mutable value, and it is important that all subsequent call to p share the same type (because they must match the stored value). Thus the type of p is '_weak1 -> int * '_weak1 where '_weak1 is a weak type variable, aka a temporary placeholder for a concrete type.

octachron
  • 17,178
  • 2
  • 16
  • 23