Will I ever understand Format? or How to print a list vertically with indentation

I am trying to devise a pretty-printer for an Env.t type. An Env.t is simply a map from identifiers to values, i.e. a Value.t IdMap.t.

  let pp fmt env =
    let open Format in
    fprintf fmt "[ @[<v 2>";
    env |> IdMap.iter
      (fun id val_ -> fprintf fmt "@[<hov 2>%s ->@ %a@]@." id Value.pp val_);
    fprintf fmt "@]]"

This gives me this kind of results:

[ result -> 42.
succ -> <function>
x -> 42.
]

However, I was hoping to have an indentation of 2 spaces from the second line on. What am I doing wrong, and would using Fmt make my life easier?

1 Like

You are flushing the pretty printer. This closes all open boxes and starts over. You wanted to write @,.

No. Except for a few nice additions, Fmt is essentially Format with the right names for concise definitions.

1 Like

Thank you. Replacing with @, gives me

[ result -> 42.
                               succ -> <function>
                               x -> 42.
                               ]

I am still unsure why.

I suspect you have an unclosed box in Value.pp.

I can’t find it. Value.pp is defined as:

  let pp fmt = let open Format in function
  | Nil -> pp_print_string fmt "nil"
  | Boolean b -> pp_print_bool fmt b
  | Number n -> pp_print_float fmt n
  | Function _ -> pp_print_string fmt "<function>"

I’ll investigate more.

I think your boxing layout is wrong you need to write
"@[<v2>[ ""]@]".

Currently you start you box after "[ " so it’s going to set the “box tab” at that column and will indent furthermore from there when you get on new lines.

I found the old tutorial Using the Format module quite useful in understanding the semantics of Format.

Cheers,
Nicolas

3 Likes

Note that it is on the new website aswell.

3 Likes

fmt might help if you shift your focus to learning its ‘design language’. I wrote a little bit about that here: How to print anything in OCaml - DEV Community

In your case suppose we want to format a type int Map.Make(String).t, i.e. a map from string to int. We can do:

module Env = Map.Make(String)

(* This can format a seq of (string, int) pairs *)
let pp_t = Fmt.(braces (vbox (seq ~sep:semi (hbox (pair ~sep:comma string int)))))

(* This can format an int Env.t by converting it into a seq of (string, int) pairs *)
let pp_t formatter env = pp_t formatter (Env.to_seq env)

This gives us output like:


# #install_printer pp_t;;
# let env = ["a", 1; "b", 2; "c", 3] |> List.to_seq |> Env.of_seq;;
val env : int Env.t = {a, 1;
                       b, 2;
                       c, 3}

Not exactly what you wanted but you can adjust it somewhat more to your needs.

4 Likes

Now @yawaramin is provoking me into offering a less wasteful answer – that is one that doesn’t go through Seq.

module Env = Map.Make(String)
let pp_binding = Fmt.(hbox @@ pair ~sep:comma string int)
let pp_env = 
  Fmt.(braces @@ vbox @@ iter_bindings ~sep:semi Env.iter pp_binding)
4 Likes

We eagerly await benchmarks exposing how wasteful the seq solution was.

2 Likes

Neither of your suggested fixes worked on their own, but I finally figured out that it was entirely my fault. I used \n in Format.printf before that. Yes, I know that the documentation says not to, but I thought that at the top level out of any box it was fine. Now thinking twice I can see why it isn’t.

I’m surprised you need a benchmark to see wasted allocations in this program. But sorry I don’t write benchmarks I write real programs.

I often see programmers getting terrible performance out of their programs because they create little allocation hells for themselves.

This is a good educational example where useless allocations can be spotted and avoided without having to perform an elaborate monkey dance. Something the good working OCaml programmer routinely does.

I’m really tired of arguing with you and your continuous FUD, but anyway. Seq allocates, yes, but allocations are very short lived (consumed immediately) and thus will mostly impact the minor heap.

I find it funny to even talk about that in the context of Format, given the size of a formater and the complexity of its internal operations. Seq would probably be tiny on a profiler. The good programmer also measures, they don’t just guess based on their mental model — unless you have the uncanny ability to predict where bottlenecks will actually be.

Now I’m off. Please stop inserting yourself in threads that don’t need your “help”.

2 Likes

There’s no FUD, allocations exist, measuring is important but getting a feel for it also is.

However more importantly if you are tired of arguing with me then maybe don’t write to me in the first place.

That’s kind of a funny comment to make, as a matter of fact you didn’t provide much help in this thread, except for derailing it in this pointless discussion.

There is also a nice document called “Format Unraveled”, if you want to learn more about Format. It can be found here

2 Likes

FWIW, I have never found @dbuenzli 's interventions to be anything but helpful, even when it concerns things that he didn’t have obvious expert knowledge about. In the case of Format, I think he has expert knowledge.

1 Like