user-defined printer in OCaml
Asked Answered
F

3

8

printf, fprintf, etc. : all accept the %a conversion.

The manual says for %a:

"user-defined printer. Takes two arguments and apply the first one to outchan (the current output channel) and to the second argument. The first argument must therefore have type out_channel -> 'b -> unit and the second 'b. The output produced by the function is therefore inserted in the output of fprintf at the current point."

I can't understand what a user-defined printer is for, and how you would implement and use it. Can someone explain the motivation and maybe provide an example?

For example, when you want to, say, print a complex data-structure, why is it not possible to just print the data-structure with a custom function directly to a string or to output?

Friseur answered 6/7, 2011 at 8:37 Comment(2)
sprintf and ksprintf take a function of unit -> 'a -> string unlike the functions you've mentioned.Barbell
My question was maybe quite imprecise. What I'd want to know is under what circumstances one should prefer to use %a instead of just printing to a string (either with printf and %s or sprintf or ksprintf etc.) and then use the string to do other things - for example to output on a channel.Friseur
F
2

If you have a function ty -> string you can use it with "%s" to print your data, so I think in realistic cases you can "just print your data structure". It might be a stylistic choice to use "%a" instead. It does seem more consistent in some ways.

On a 32-bit system, strings are limited to around 16MB in length. So you could imagine a case where the "%a" would work while "%s" would fail: if the intermediate string is longer than this. I've never had this come up in practice, though. I just use "%s", myself.

From answered 6/7, 2011 at 10:15 Comment(0)
S
5

What do you mean by "just print the complex data-structure"? You can do that once you defined a function converting your data-structure to string. It's also possible to "dump" the data-structure with a "default representation" (see, http://caml.inria.fr/cgi-bin/hump.en.cgi?sort=0&browse=139) but that's more for debugging than anything else.

Having said that; a very simple example for %a:

type ty = A | B

let ty_to_string = function
  | A -> "A"
  | B -> "B"

let print_ty chan v = output_string chan (ty_to_string v)

let _ = Printf.printf "%a" print_ty A
Serendipity answered 6/7, 2011 at 9:46 Comment(2)
What I have trouble understanding is why one would prefer this over say let _ = Printf.printf "%s" (ty_to_string A) or let _ = Printf.fprintf stdout "%s" (ty_to_string A). At least the latter gives also some kind of abstraction as to which channel to print to, similar to using %a in the above example?Friseur
The thing is, for larger structures conversion to string can be costly (think: string concatenations). If possible, it's much more efficient to write directly to the channel. And when you have functions like that %a comes in naturally.Serendipity
F
2

If you have a function ty -> string you can use it with "%s" to print your data, so I think in realistic cases you can "just print your data structure". It might be a stylistic choice to use "%a" instead. It does seem more consistent in some ways.

On a 32-bit system, strings are limited to around 16MB in length. So you could imagine a case where the "%a" would work while "%s" would fail: if the intermediate string is longer than this. I've never had this come up in practice, though. I just use "%s", myself.

From answered 6/7, 2011 at 10:15 Comment(0)
D
2

Using %a allows the printing to go directly to the output channel, as opposed to "%s" and stringifying the value you want to print and then printing it.

The distinction seems to be entirely a matter of efficiency - why allocate a potentially large string (or use a buffer, with its exponential resizing and copying) when it's both possible and reasonable to send the serialized data directly to the output channel? Jeffrey points out quite correctly that very large serializations could fail on 32-bit systems because of string length.

I use %a often in my code, using Batteries' composable printing functions to build custom printers for my values:

let range_print oc r = 
  let print_one oc (a,b) = fprintf oc "%d:%d" a b in
  List.print ~first:"" ~last:"" ~sep:"," print_one oc r
let print_rule print_pred print_dec oc r = 
  fprintf oc "%a,%a" print_pred r.pred print_dec r.dec
let print_item oc x = print_rule range_print Int.print oc x in
...
printf "IN : %a\nOUT: %a\n" print_item a print_item b;
Dipsomania answered 11/7, 2011 at 17:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.