λ. 1. Background
It’s a pity that I had totally missed Gabriel’s post The 6 parameters of (’a, ’b, ’c, ’d, ’e, ’f) format6 written after reviewing the GADT implementation of format functions, before my previous post on format6. I am reluctant to rewrite when many new topics pending in my mind. However, just after publishing the previous blog, I am thinking of how to make it clear and concise. This time, I use diagrams to explain format6
.
λ. 2. Prelude
To simplify the examples, I make the format string "%d %a"
fixed for printing and "%d %r"
for scanning. There is only one type foo
and one variant Foo
. All the user-defined reader and printers are here: scan_foo
out_foo
pp_foo
string_of_foo
, to satisfy low_level devices.
# open CamlinternalFormatBasics
# type foo = Foo
type foo = Foo
# let scan_foo ic = Scanf.bscanf ic "%s" (function | "Foo" -> Foo | _ -> raise (Scanf.Scan_failure "Foo"))
val scan_foo : Scanf.Scanning.scanbuf -> foo = <fun>
# let out_foo oc foo = Printf.fprintf oc "%s" (match foo with | Foo -> "Foo" )
val out_foo : out_channel -> foo -> unit = <fun>
# let pp_foo fmt foo = Format.fprintf fmt "%s" (match foo with | Foo -> "Foo")
val pp_foo : Format.formatter -> foo -> unit = <fun>
# let string_of_foo () = function | Foo -> "Foo"
val string_of_foo : unit -> foo -> string = <fun>
In this post, I just use four functions. They are:
# Scanf.sscanf
- : string -> ('a, 'b, 'c, 'd) Scanf.scanner = <fun>
# Printf.printf
- : ('a, out_channel, unit) format -> 'a = <fun>
# Format.fprintf
- : Format.formatter -> ('a, Format.formatter, unit) format -> 'a = <fun>
# Format.ksprintf
- : (string -> 'a) -> ('b, unit, string, 'a) format4 -> 'b = <fun>
The related format
types are
# #show Scanf.scanner
type nonrec ('a, 'b, 'c, 'd) scanner =
('a, Scanf.Scanning.scanbuf, 'b, 'c, 'a -> 'd, 'd) format6 -> 'c
# #show format
type nonrec ('a, 'b, 'c) format = ('a, 'b, 'c, 'c) format4
# #show format4
type nonrec ('a, 'b, 'c, 'd) format4 = ('a, 'b, 'c, 'c, 'c, 'd) format6
# #show format6
type nonrec ('a, 'b, 'c, 'd, 'e, 'f) format6 =
Format of ('a, 'b, 'c, 'd, 'e, 'f) fmt * string
I think a better way to think of ('a, 'b, 'c, 'd, 'e, 'f) format6
is to categorize type parameters (always indexed by format6
) by who determines it.
First, the first argument and the return type of user-define functions (appearing in 'a
'd
) are determined by printf and scanf functions
For scanf functions:
- 1st
'a
is determined by format strings - 2nd ‘b is determined by scanf functions
- 4th
'd
is determined by format strings - 6th ‘f is determined by the return type of receiver functions
The receiver function always matches the format string. It’s less interesting to argue which one is the first impetus. I just choose format strings to determine for simplicity.
For printf functions:
- 1st
'a
are determined by format strings - 2nd ‘b are determined by printf functions
- 6th ‘f are determined by printf
What’s more, for k*printf
functtion:
- 3th ‘c are determined by
k*printf
- 6th ‘f are determined by the return type of the continuation function.
λ. 3. Scanf
# Scanf.sscanf
- : string -> ('a, 'b, 'c, 'd) Scanf.scanner = <fun>
# let fmt = format_of_string "%d %r" in let _ = Scanf.sscanf "42 Foo" fmt scan_foo (fun d r -> true) in fmt
- : (int -> foo -> bool, Scanf.Scanning.scanbuf, '_weak1,
(Scanf.Scanning.scanbuf -> foo) -> (int -> foo -> bool) -> bool,
(int -> foo -> bool) -> bool, bool)
format6
=
Format
(Int (Int_d, No_padding, No_precision,
Char_literal (' ', Reader End_of_format)),
"%d %r")
The diagram shows that
- sscanf determines the types of both input source “42 Foo” and low-level device scanbuf of
foo_reader
- 4th type parameter
'd
matches the arguments after the format strings 'd
ends with'a -> 'f
- the return type of the receiver function determines the return type of
'd
,'f
, and even Scanf.sscanf
λ. 4. Printf
# Printf.printf
- : ('a, out_channel, unit) format -> 'a = <fun>
# let fmt = format_of_string "%d %a" in let _ = Printf.printf fmt 42 out_foo Foo in fmt
42 Foo
- : (int -> (out_channel -> foo -> unit) -> foo -> unit, out_channel,
unit, unit, unit, unit)
format6
=
Format
(Int (Int_d, No_padding, No_precision,
Char_literal (' ', Alpha End_of_format)),
"%d %a")
The diagram shows printf determines the type of out_channel and unit appearing in 1st type parameter 'a
and 6th type parameter 'f
.
λ. 5. Format
# Format.fprintf
- : Format.formatter -> ('a, Format.formatter, unit) format -> 'a = <fun>
# let fmt = format_of_string "%d %a" in let _f fmter = Format.fprintf fmter fmt 42 pp_foo Foo in fmt
- : (int -> (Format.formatter -> foo -> unit) -> foo -> unit,
Format.formatter, unit, unit, unit, unit)
format6
=
Format
(Int (Int_d, No_padding, No_precision,
Char_literal (' ', Alpha End_of_format)),
"%d %a")
The diagram for Format.fprintf
doesn’t differ much with Printf.printf
. fprintf determines the type of fmter, Format.formatter, and all units.
λ. 6. kprintf
# Format.ksprintf
- : (string -> 'a) -> ('b, unit, string, 'a) format4 -> 'b = <fun>
# let fmt = format_of_string "%d %a" in let _ = Format.ksprintf (fun _s -> true) fmt 42 string_of_foo Foo in fmt
- : (int -> (unit -> foo -> string) -> foo -> bool, unit, string, string,
string, bool)
format6
=
Format
(Int (Int_d, No_padding, No_precision,
Char_literal (' ', Alpha End_of_format)),
"%d %a")
The diagram shows Format.ksprintf determines the type of the input of the continuation and two heads of foo_printer. The return types of the continuation determines 6th type parameter ‘f.
λ. 7. Others
Most time of this blog is spent on taming mermaid, the diagram library. You can still see unsolved artifacts like single quotes for strings, diagrams without titles, and unhappy overlappings. The choice of dot lines or color nodes for presenting connections (determining), is to reduce total lines and to keep the layout engine happy. I had (nicer) hand-painting diagrams on iPad before these two posts on format6
.
The catch-22 of Diagrams and Charts:
Diagramming and charting is a gigantic waste of developer time, but not having diagrams
ruins productivity.
-- mermaid
λ. Reference
- The 6 parameters of (’a, ’b, ’c, ’d, ’e, ’f) format6 http://gallium.inria.fr/blog/format6/
- Mermaid document https://mermaid-js.github.io/mermaid/#/