Format strings are magical in OCaml: if a string has the right type, then the language turns it into some structured data including a function over the typed arguments that the format string requires:
# ("%d %s %d": ('a, 'b, 'c, 'd, 'e, 'f) format6);;
- : (int -> string -> int -> 'f, 'b, 'c, 'e, 'e, 'f) format6 =
CamlinternalFormatBasics.Format
(CamlinternalFormatBasics.Int (CamlinternalFormatBasics.Int_d,
CamlinternalFormatBasics.No_padding, CamlinternalFormatBasics.No_precision,
CamlinternalFormatBasics.Char_literal (' ',
CamlinternalFormatBasics.String (CamlinternalFormatBasics.No_padding,
CamlinternalFormatBasics.Char_literal (' ',
CamlinternalFormatBasics.Int (CamlinternalFormatBasics.Int_d,
CamlinternalFormatBasics.No_padding,
CamlinternalFormatBasics.No_precision,
CamlinternalFormatBasics.End_of_format))))),
"%d %s %d")
You can get this without reconstructing that type from Stdlib's ^^ and format_of_string.
If you're willing to work with types with suspicious names like Camlinternal, you technically can:
# match format_of_string "hi %d" with CamlinternalFormatBasics.Format (CamlinternalFormatBasics.String_literal (str, _), _) -> str | _ -> "???";; - : string = "hi "
But realistically the upside here is just that you can store format strings in variables:
let f x n =
let fmt =
format_of_string
(match x with
| `En -> "Example number %d"
| `Ru -> "Пример №%d")
in
Printf.sprintf fmt n
let () =
assert (f `En 20 = "Example number 20");
assert (f `Ru 47 = "Пример №47")
Less-magical varargs can be simulated with higher order functions, example adapted from stackoverflow:
module Print = struct
let print f = f ""
let s y x g = g (x ^ y)
let i y x g = g (x ^ string_of_int y)
let endl a = print_endline a
end
let () = Print.(print (s "Hello") (s ", ") (s "world!") endl)
let () = Print.(print (s "A number: ") (i 100) endl)
Or with a preprocessor, example using ppx_format which does this:
let target = "world"
let count = 100
let () = Printf.printf {%i|Hello, {%s target} x {%d count}!{%s "\n"}|}
You can also accept runtime format strings that match a provided format string's type:
let () =
let fmt = Scanf.format_from_string Sys.argv.(1) "%s%d" in
Printf.printf (fmt ^^ "\n") "star" 3
As used:
$ ocaml o128.ml "%ss? I have %d of them." stars? I have 3 of them. $ ocaml o128.ml "%s count: %d" star count: 3 $ ocaml o128.ml "%s invalid" Exception: Stdlib.Scanf.Scan_failure "bad input: format type mismatch between \"%s invalid\" and \"%s%d\"".