John Wickerson
John Wickerson

Reputation: 1232

Ocaml's sprintf doesn't work with %a format specifier

It seems that sprintf does not allow %a in its format specifier. Is this right? If so, why is it like this, and is there a workaround?

Example. I have a long and complicated datatype:

type t = Foo | Baz

I have a pretty-printer for this type, that sends a string representation of its values to an arbitrary out_channel:

let pp_t oc = function
  | Foo -> fprintf oc "Foo"
  | Baz -> fprintf oc "Baz"

If I then write

let _ = printf "%a=%d" pp_t Foo 2

or

let _ = eprintf "%a=%d" pp_t Foo 2

then everything works fine -- the message "Foo=2" ends up on my stdout or my stderr as expected. But if I write

let s = sprintf "%a=%d" pp_t Foo 2

then I get a compilation error, complaining that the argument pp_t has type out_channel -> t -> unit but an expression was expected of type unit -> 'a -> string.

Is it simply not possible to use %a inside a format specifier for sprintf?

Upvotes: 1

Views: 1256

Answers (2)

ivg
ivg

Reputation: 35210

Short answer

Use the Format module for the pretty printing and the Format.asprintf function instead of sprintf for constructing strings (I usually just do open Format at the beginning of a file).

Long answer

It is possible to use the %a conversion specification with sprintf. However, the required function has a type that is different from what is required for Printf.fprintf and Printf.printf.

All pretty-printers (functions that satisfy a type of a value, that is required by the %a conversion) encode the type of output in their types. So for different families of printf functions you need to use different pretty-printing functions.

In the case of the Format module pretty-printers, you need a function of type formatter -> 'a -> unit. This is the type that is also required by the top-level and the debugger, and it is also a most common type of a pretty-printing function (libraries such as Core, as well as many other libraries, provide pretty-printers that satisfy this signature). The Format module also provides the asprintf function, that, unlike the sprintf function, plays nice with the %a conversion (i.e., it has the same formatter -> 'a -> unit type).

In the case of Printf.printf the type of a pretty-printer should be out_channel -> 'a -> unit. This type is much less versatile and thus is much less popular than the Format's one. Very few libraries provide one.

Finally, the Printf.sprintf function requires a printer of type unit -> 'a -> string. Usually, the only way to get such pretty printer is to write it manually (sometimes using Printf.asprintf). In your case it would be:

let pp_t () = function
  | Foo -> "Foo"
  | Baz -> "Baz"

Upvotes: 4

Daniel Bünzli
Daniel Bünzli

Reputation: 5167

In order to get away with specific datatypes showing up in your formatting functions you should always use the Format module and devise pretty printers for your types that have the signature val pp_t : Format.formatter -> t -> unit; this is also the signature the toplevel asks for to install custom printers with #install_printer for your datatypes.

Equipped with such a function you can then simply use Format.asprintf with "%a".

Upvotes: 2

Related Questions