Search code examples
ocamlvariadic-functions

Custom printer to string


I defined a custom pretty-printer that takes a message, customise it and prints it:

let mypp ppf msg =
  Format.fprintf ppf "Hello";
  Format.fprintf ppf msg

Now, I wanted to use it to print to a string but since I want to use it multiple times I wanted to put it in a function:

let myspp msg =
  let _ = mypp Format.str_formatter msg in
  Format.flush_str_formatter ()

But I can't write what I want:

12 |   let s = myspp "Bleh %s" "world" in
               ^^^^^
Error: This function has type ('a, Format.formatter, unit) format -> string
       It is applied to too many arguments; maybe you forgot a `;'.

Even worse, if I delete the argument:

let () =
  let s = myspp "Bleh %s" in
  Format.eprintf "---%s---@." s

Results in:

---Hello: ---

The formatting string disappeared.

I know I'm missing something but can't find it. I tried using kfprintf but didn't have good results. Maybe I need to change my original function?

It should be noted that if I don't use it in a function it works as wanted:

let () = 
  mypp Format.str_formatter "Blah %s" "blih";
  Format.eprintf "---%s---@." (Format.flush_str_formatter ())

Results in:

---Hello: Blah blih---

Solution

  • Since you want to run some function after that all format arguments have been provided, the only option is to use kfprintf:

    let to_string msg =
      let b = Buffer.create 17 in
      let ppf = Format.formatter_of_buffer b in
      Format.fprintf ppf "Hello: ";
      Format.kfprintf (fun ppf ->
        Format.pp_print_flush ppf ();
        Buffer.contents b
      ) ppf msg
    
      let s = to_string "%d + %d = %d" 1 2 3
    

    It is also better to avoid Format.str_formatter since this avoid introducing a global mutable state in your program.

    EDIT:

    If the important point is to reuse the mypp function, the simplest fix is to add a continuation argument to mypp:

    let kmypp k ppf msg =
      Format.fprintf ppf "Hello";
      Format.kfprintf k ppf msg
    
    let to_string msg =
      let b = Buffer.create 17 in
      let ppf = Format.formatter_of_buffer b in
      kmypp (fun ppf ->
        Format.pp_print_flush ppf ();
        Buffer.contents b
      ) ppf msg