## MLton 20241230

# Printf

Programmers coming from C or Java often ask if 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.

    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.

    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 <spec1> ... <specn> $ <arg1> ... <argm>

where each `<speci>` is either a specifier like `B`, `I`, or `R`, or is an inline string, like ``"foo"`. A backtick (```) must precede each inline string. Each `<argi>` 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:

    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.

    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

- Functional Unparsing