| 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)> |