Printf ====== Programmers coming from C or Java often ask if <:StandardML:Standard ML> has a `printf` function. It does not. However, it is possible to implement your own version with only a few lines of code. Here is a definition for `printf` and `fprintf`, along with format specifiers for booleans, integers, and reals. [source,sml] ---- structure Printf = struct fun $ (_, f) = f (fn p => p ()) ignore fun fprintf out f = f (out, id) val printf = fn z => fprintf TextIO.stdOut z fun one ((out, f), make) g = g (out, fn r => f (fn p => make (fn s => r (fn () => (p (); TextIO.output (out, s)))))) fun ` x s = one (x, fn f => f s) fun spec to x = one (x, fn f => f o to) val B = fn z => spec Bool.toString z val I = fn z => spec Int.toString z val R = fn z => spec Real.toString z end ---- Here's an example use. [source,sml] ---- val () = printf `"Int="I`" Bool="B`" Real="R`"\n" $ 1 false 2.0 ---- This prints the following. ---- Int=1 Bool=false Real=2.0 ---- In general, a use of `printf` looks like ---- printf ... $ ... ---- where each `` is either a specifier like `B`, `I`, or `R`, or is an inline string, like ++`"foo"++. A backtick (+`+) must precede each inline string. Each `` must be of the appropriate type for the corresponding specifier. SML `printf` is more powerful than its C counterpart in a number of ways. In particular, the function produced by `printf` is a perfectly ordinary SML function, and can be passed around, used multiple times, etc. For example: [source,sml] ---- val f: int -> bool -> unit = printf `"Int="I`" Bool="B`"\n" $ val () = f 1 true val () = f 2 false ---- The definition of `printf` is even careful to not print anything until it is fully applied. So, examples like the following will work as expected. ---- val f: int -> bool -> unit = printf `"Int="I`" Bool="B`"\n" $ 13 val () = f true val () = f false ---- It is also easy to define new format specifiers. For example, suppose we wanted format specifiers for characters and strings. ---- val C = fn z => spec Char.toString z val S = fn z => spec (fn s => s) z ---- One can define format specifiers for more complex types, e.g. pairs of integers. ---- val I2 = fn z => spec (fn (i, j) => concat ["(", Int.toString i, ", ", Int.toString j, ")"]) z ---- Here's an example use. ---- val () = printf `"Test "I2`" a string "S`"\n" $ (1, 2) "hello" ---- == Printf via <:Fold:> == `printf` is best viewed as a special case of variable-argument <:Fold:> that inductively builds a function as it processes its arguments. Here is the definition of a `Printf` structure in terms of fold. The structure is equivalent to the above one, except that it uses the standard `$` instead of a specialized one. [source,sml] ---- structure Printf = struct fun fprintf out = Fold.fold ((out, id), fn (_, f) => f (fn p => p ()) ignore) val printf = fn z => fprintf TextIO.stdOut z fun one ((out, f), make) = (out, fn r => f (fn p => make (fn s => r (fn () => (p (); TextIO.output (out, s)))))) val ` = fn z => Fold.step1 (fn (s, x) => one (x, fn f => f s)) z fun spec to = Fold.step0 (fn x => one (x, fn f => f o to)) val B = fn z => spec Bool.toString z val I = fn z => spec Int.toString z val R = fn z => spec Real.toString z end ---- Viewing `printf` as a fold opens up a number of possibilities. For example, one can name parts of format strings using the fold idiom for naming sequences of steps. ---- val IB = fn u => Fold.fold u `"Int="I`" Bool="B val () = printf IB`" "IB`"\n" $ 1 true 3 false ---- One can even parametrize over partial format strings. ---- fun XB X = fn u => Fold.fold u `"X="X`" Bool="B val () = printf (XB I)`" "(XB R)`"\n" $ 1 true 2.0 false ---- == Also see == * <:PrintfGentle:> *