Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | Printf |
2 | ====== | |
3 | ||
4 | Programmers coming from C or Java often ask if | |
5 | <:StandardML:Standard ML> has a `printf` function. It does not. | |
6 | However, it is possible to implement your own version with only a few | |
7 | lines of code. | |
8 | ||
9 | Here is a definition for `printf` and `fprintf`, along with format | |
10 | specifiers for booleans, integers, and reals. | |
11 | ||
12 | [source,sml] | |
13 | ---- | |
14 | structure Printf = | |
15 | struct | |
16 | fun $ (_, f) = f (fn p => p ()) ignore | |
17 | fun fprintf out f = f (out, id) | |
18 | val printf = fn z => fprintf TextIO.stdOut z | |
19 | fun one ((out, f), make) g = | |
20 | g (out, fn r => | |
21 | f (fn p => | |
22 | make (fn s => | |
23 | r (fn () => (p (); TextIO.output (out, s)))))) | |
24 | fun ` x s = one (x, fn f => f s) | |
25 | fun spec to x = one (x, fn f => f o to) | |
26 | val B = fn z => spec Bool.toString z | |
27 | val I = fn z => spec Int.toString z | |
28 | val R = fn z => spec Real.toString z | |
29 | end | |
30 | ---- | |
31 | ||
32 | Here's an example use. | |
33 | ||
34 | [source,sml] | |
35 | ---- | |
36 | val () = printf `"Int="I`" Bool="B`" Real="R`"\n" $ 1 false 2.0 | |
37 | ---- | |
38 | ||
39 | This prints the following. | |
40 | ||
41 | ---- | |
42 | Int=1 Bool=false Real=2.0 | |
43 | ---- | |
44 | ||
45 | In general, a use of `printf` looks like | |
46 | ||
47 | ---- | |
48 | printf <spec1> ... <specn> $ <arg1> ... <argm> | |
49 | ---- | |
50 | ||
51 | where each `<speci>` is either a specifier like `B`, `I`, or `R`, or | |
52 | is an inline string, like ++`"foo"++. A backtick (+`+) | |
53 | must precede each inline string. Each `<argi>` must be of the | |
54 | appropriate type for the corresponding specifier. | |
55 | ||
56 | SML `printf` is more powerful than its C counterpart in a number of | |
57 | ways. In particular, the function produced by `printf` is a perfectly | |
58 | ordinary SML function, and can be passed around, used multiple times, | |
59 | etc. For example: | |
60 | ||
61 | [source,sml] | |
62 | ---- | |
63 | val f: int -> bool -> unit = printf `"Int="I`" Bool="B`"\n" $ | |
64 | val () = f 1 true | |
65 | val () = f 2 false | |
66 | ---- | |
67 | ||
68 | The definition of `printf` is even careful to not print anything until | |
69 | it is fully applied. So, examples like the following will work as | |
70 | expected. | |
71 | ||
72 | ---- | |
73 | val f: int -> bool -> unit = printf `"Int="I`" Bool="B`"\n" $ 13 | |
74 | val () = f true | |
75 | val () = f false | |
76 | ---- | |
77 | ||
78 | It is also easy to define new format specifiers. For example, suppose | |
79 | we wanted format specifiers for characters and strings. | |
80 | ||
81 | ---- | |
82 | val C = fn z => spec Char.toString z | |
83 | val S = fn z => spec (fn s => s) z | |
84 | ---- | |
85 | ||
86 | One can define format specifiers for more complex types, e.g. pairs of | |
87 | integers. | |
88 | ||
89 | ---- | |
90 | val I2 = | |
91 | fn z => | |
92 | spec (fn (i, j) => | |
93 | concat ["(", Int.toString i, ", ", Int.toString j, ")"]) | |
94 | z | |
95 | ---- | |
96 | ||
97 | Here's an example use. | |
98 | ||
99 | ---- | |
100 | val () = printf `"Test "I2`" a string "S`"\n" $ (1, 2) "hello" | |
101 | ---- | |
102 | ||
103 | ||
104 | == Printf via <:Fold:> == | |
105 | ||
106 | `printf` is best viewed as a special case of variable-argument | |
107 | <:Fold:> that inductively builds a function as it processes its | |
108 | arguments. Here is the definition of a `Printf` structure in terms of | |
109 | fold. The structure is equivalent to the above one, except that it | |
110 | uses the standard `$` instead of a specialized one. | |
111 | ||
112 | [source,sml] | |
113 | ---- | |
114 | structure Printf = | |
115 | struct | |
116 | fun fprintf out = | |
117 | Fold.fold ((out, id), fn (_, f) => f (fn p => p ()) ignore) | |
118 | ||
119 | val printf = fn z => fprintf TextIO.stdOut z | |
120 | ||
121 | fun one ((out, f), make) = | |
122 | (out, fn r => | |
123 | f (fn p => | |
124 | make (fn s => | |
125 | r (fn () => (p (); TextIO.output (out, s)))))) | |
126 | ||
127 | val ` = | |
128 | fn z => Fold.step1 (fn (s, x) => one (x, fn f => f s)) z | |
129 | ||
130 | fun spec to = Fold.step0 (fn x => one (x, fn f => f o to)) | |
131 | ||
132 | val B = fn z => spec Bool.toString z | |
133 | val I = fn z => spec Int.toString z | |
134 | val R = fn z => spec Real.toString z | |
135 | end | |
136 | ---- | |
137 | ||
138 | Viewing `printf` as a fold opens up a number of possibilities. For | |
139 | example, one can name parts of format strings using the fold idiom for | |
140 | naming sequences of steps. | |
141 | ||
142 | ---- | |
143 | val IB = fn u => Fold.fold u `"Int="I`" Bool="B | |
144 | val () = printf IB`" "IB`"\n" $ 1 true 3 false | |
145 | ---- | |
146 | ||
147 | One can even parametrize over partial format strings. | |
148 | ||
149 | ---- | |
150 | fun XB X = fn u => Fold.fold u `"X="X`" Bool="B | |
151 | val () = printf (XB I)`" "(XB R)`"\n" $ 1 true 2.0 false | |
152 | ---- | |
153 | ||
154 | ||
155 | == Also see == | |
156 | ||
157 | * <:PrintfGentle:> | |
158 | * <!Cite(Danvy98, Functional Unparsing)> |