This page provides a gentle introduction and derivation of Printf, with sections and arrangement more suitable to a talk.

Introduction

SML does not have printf. Could we define it ourselves?

```val () = printf ("here's an int %d and a real %f.\n", 13, 17.0)
val () = printf ("here's three values (%d, %f, %f).\n", 13, 17.0, 19.0)
```

What could the type of printf be?

This obviously can’t work, because SML functions take a fixed number of arguments. Actually they take one argument, but if that’s a tuple, it can only have a fixed number of components.

From tupling to currying

What about currying to get around the typing problem?

```val () = printf "here's an int %d and a real %f.\n" 13 17.0
val () = printf "here's three values (%d, %f, %f).\n" 13 17.0 19.0
```

That fails for a similar reason. We need two types for printf.

```val printf: string -> int -> real -> unit
val printf: string -> int -> real -> real -> unit```

This can’t work, because printf can only have one type. SML doesn’t support programmer-defined overloading.

Even without worrying about number of arguments, there is another problem. The type of printf depends on the format string.

```val () = printf "here's an int %d and a real %f.\n" 13 17.0
val () = printf "here's a real %f and an int %d.\n" 17.0 13
```

Now we need

```val printf: string -> int -> real -> unit
val printf: string -> real -> int -> unit```

Again, this can’t possibly working because SML doesn’t have overloading, and types can’t depend on values.

Idea: express type information in the format string

If we express type information in the format string, then different uses of printf can have different types.

```type 'a t  (* the type of format strings *)
val printf: 'a t -> 'a
infix D F
val fs1: (int -> real -> unit) t = "here's an int "D" and a real "F".\n"
val fs2: (int -> real -> real -> unit) t =
"here's three values ("D", "F", "F").\n"
val () = printf fs1 13 17.0
val () = printf fs2 13 17.0 19.0
```

Now, our two calls to printf type check, because the format string specializes printf to the appropriate type.

The types of format characters

What should the type of format characters D and F be? Each format character requires an additional argument of the appropriate type to be supplied to printf.

Idea: guess the final type that will be needed for printf the format string and verify it with each format character.

```type ('a, 'b) t   (* 'a = rest of type to verify, 'b = final type *)
val ` : string -> ('a, 'a) t  (* guess the type, which must be verified *)
val D: (int -> 'a, 'b) t * string -> ('a, 'b) t  (* consume an int *)
val F: (real -> 'a, 'b) t * string -> ('a, 'b) t  (* consume a real *)
val printf: (unit, 'a) t -> 'a
```

Don’t worry. In the end, type inference will guess and verify for us.

Understanding guess and verify

Now, let’s build up a format string and a specialized printf.

```infix D F
val f0 = `"here's an int "
val f1 = f0 D " and a real "
val f2 = f1 F ".\n"
val p = printf f2
```

These definitions yield the following types.

```val f0: (int -> real -> unit, int -> real -> unit) t
val f1: (real -> unit, int -> real -> unit) t
val f2: (unit, int -> real -> unit) t
val p: int -> real -> unit
```

So, p is a specialized printf function. We could use it as follows

```val () = p 13 17.0
val () = p 14 19.0
```

Type checking this using a functor

```signature PRINTF =
sig
type ('a, 'b) t
val ` : string -> ('a, 'a) t
val D: (int -> 'a, 'b) t * string -> ('a, 'b) t
val F: (real -> 'a, 'b) t * string -> ('a, 'b) t
val printf: (unit, 'a) t -> 'a
end

functor Test (P: PRINTF) =
struct
open P
infix D F

val () = printf (`"here's an int "D" and a real "F".\n") 13 17.0
val () = printf (`"here's three values ("D", "F ", "F").\n") 13 17.0 19.0
end
```

Implementing Printf

Think of a format character as a formatter transformer. It takes the formatter for the part of the format string before it and transforms it into a new formatter that first does the left hand bit, then does its bit, then continues on with the rest of the format string.

```structure Printf: PRINTF =
struct
datatype ('a, 'b) t = T of (unit -> 'a) -> 'b

fun printf (T f) = f (fn () => ())

fun ` s = T (fn a => (print s; a ()))

fun D (T f, s) =
T (fn g => f (fn () => fn i =>
(print (Int.toString i); print s; g ())))

fun F (T f, s) =
T (fn g => f (fn () => fn i =>
(print (Real.toString i); print s; g ())))
end
```

Testing printf

```structure Z = Test (Printf)
```

User-definable formats

The definition of the format characters is pretty much the same. Within the Printf structure we can define a format character generator.

```val newFormat: ('a -> string) -> ('a -> 'b, 'c) t * string -> ('b, 'c) t =
fn toString => fn (T f, s) =>
T (fn th => f (fn () => fn a => (print (toString a); print s ; th ())))
val D = fn z => newFormat Int.toString z
val F = fn z => newFormat Real.toString z
```

A core Printf

We can now have a very small PRINTF signature, and define all the format strings externally to the core module.

```signature PRINTF =
sig
type ('a, 'b) t
val ` : string -> ('a, 'a) t
val newFormat: ('a -> string) -> ('a -> 'b, 'c) t * string -> ('b, 'c) t
val printf: (unit, 'a) t -> 'a
end

structure Printf: PRINTF =
struct
datatype ('a, 'b) t = T of (unit -> 'a) -> 'b

fun printf (T f) = f (fn () => ())

fun ` s = T (fn a => (print s; a ()))

fun newFormat toString (T f, s) =
T (fn th =>
f (fn () => fn a =>
(print (toString a)
; print s
; th ())))
end
```

Extending to fprintf

One can implement fprintf by threading the outstream through all the transformers.

```signature PRINTF =
sig
type ('a, 'b) t
val ` : string -> ('a, 'a) t
val fprintf: (unit, 'a) t * TextIO.outstream -> 'a
val newFormat: ('a -> string) -> ('a -> 'b, 'c) t * string -> ('b, 'c) t
val printf: (unit, 'a) t -> 'a
end

structure Printf: PRINTF =
struct
type out = TextIO.outstream
val output = TextIO.output

datatype ('a, 'b) t = T of (out -> 'a) -> out -> 'b

fun fprintf (T f, out) = f (fn _ => ()) out

fun printf t = fprintf (t, TextIO.stdOut)

fun ` s = T (fn a => fn out => (output (out, s); a out))

fun newFormat toString (T f, s) =
T (fn g =>
f (fn out => fn a =>
(output (out, toString a)
; output (out, s)
; g out)))
end
```

Notes

• Lesson: instead of using dependent types for a function, express the the dependency in the type of the argument.

• If printf is partially applied, it will do the printing then and there. Perhaps this could be fixed with some kind of terminator.

A syntactic or argument terminator is not necessary. A formatter can either be eager (as above) or lazy (as below). A lazy formatter accumulates enough state to print the entire string. The simplest lazy formatter concatenates the strings as they become available:

```structure PrintfLazyConcat: PRINTF =
struct
datatype ('a, 'b) t = T of (string -> 'a) -> string -> 'b

fun printf (T f) = f print ""

fun ` s = T (fn th => fn s' => th (s' ^ s))

fun newFormat toString (T f, s) =
T (fn th =>
f (fn s' => fn a =>
th (s' ^ toString a ^ s)))
end
```

It is somewhat more efficient to accumulate the strings as a list:

```structure PrintfLazyList: PRINTF =
struct
datatype ('a, 'b) t = T of (string list -> 'a) -> string list -> 'b

fun printf (T f) = f (List.app print o List.rev) []

fun ` s = T (fn th => fn ss => th (s::ss))

fun newFormat toString (T f, s) =
T (fn th =>
f (fn ss => fn a =>
th (s::toString a::ss)))
end
```