Format conversion doesn't support padding

E.g.

type number = Int of int | Float of float

let pp_number f = function
  | Int i -> Format.pp_print_int f i
  | Float fl -> Format.pp_print_float f fl

Format.printf "%20a\n" pp_number (Int 1)

Prints:

1

I.e. no padding. If I inspect the format string:

# format_of_string "%20a\n";;
- : (('_weak1 -> '_weak2 -> '_weak3) -> '_weak2 -> '_weak4, '_weak1, '_weak3,
     '_weak5, '_weak5, '_weak4)
    format6
=
CamlinternalFormatBasics.Format
 (CamlinternalFormatBasics.Alpha
   (CamlinternalFormatBasics.Char_literal ('\n',
     CamlinternalFormatBasics.End_of_format)),
 "%20a\n")

There is no padding instruction, as opposed to a format string with an int conversion which contains the padding instruction:

format_of_string "%20d\n";;
- : (int -> '_weak6, '_weak7, '_weak8, '_weak9, '_weak9, '_weak6) format6 =
CamlinternalFormatBasics.Format
 (CamlinternalFormatBasics.Int (CamlinternalFormatBasics.Int_d,
   CamlinternalFormatBasics.Lit_padding (CamlinternalFormatBasics.Right, 20),
   CamlinternalFormatBasics.No_precision,
   CamlinternalFormatBasics.Char_literal ('\n',
    CamlinternalFormatBasics.End_of_format)),
 "%20d\n")

Does anyone know why it doesn’t work for format conversions?

The padding (and precision) there are part of the Int constructor (and some others) because their meaning is defined for values of those types:

For the Alpha constructor, which corresponds to %a in the format string and may delegate to any printing code, it’s not defined how those should work:

The padding value outside on the %a would somehow have to propagate inside to the %d and apply there. Maybe padding could be implemented generically (not sure if it could or it has some type-specific behavior right now), but the same criticism would apply to flags and precision as well. Those certainly cannot work for arbitrary specifiers.

But indeed, I don’t see it documented, for which specifiers flags/padding/precision work and for which they silently don’t. That’s only apparent from looking at those internals.

1 Like

Fun fact: there is a -strict-formats option that rejects many invalid formats instead of silently doing something.

ocaml -strict-formats
# Printf.printf "%20a" (fun _ -> print_int) 42;;
Error: invalid format "%20a": at character number 0,
`padding' is incompatible with 'a' in sub-format "%20a"

Benoît Vaugon and myself implemented this when we rewrote formatting support to use GADTs (Benoît’s idea). It was not enabled by default because various programs in the wild would have started failing (they contained invalid but innocuous formats and just worked), and we wanted excellent compatibility by default to sell the (large) change. Ideally this could be turned into a warning, but we never got around to doing this work. Volunteers welcome :slight_smile: (Adding warnings is not a magic bullet because if you enable them by default, and people set errors on warnings, then you are back to breaking code again.)

11 Likes

Out of curiosity I had a look and discovered that both its documentation and implementation actually promise to remove the non-strict behavior altogether:

Then it seems OCaml 5 is the perfect time to turn this on.

Indeed, I opened an issue to bring attention to it: `-strict-formats` by default · Issue #11538 · ocaml/ocaml · GitHub.

1 Like