[MLton] printf with no infixes

Vesa Karvonen vesa.karvonen@cs.helsinki.fi
Fri, 26 Aug 2005 00:22:12 +0300


Quoting Stephen Weeks:
[...Thorough comparison...]
> One requires strings before directives and the other requires
> directives first.  And the signatures make it clear to me that
> there is no substantive difference.

Sure, this is mostly about notational differences rather than some deep
semantic stuff. The alternative syntax is a simplifying rewrite of the
original; I wrote the implementation basically by deleting a few characters
from the original implementation (hence "quick") and adding the ` specifier.
The main difference between the two is that the rewrite omits the default
string argument (as you noted). This makes the approach simpler and, IMO,
more orthogonal. In particular, the (backtick) ` specifier is really no
different from other conversion specifiers. The alternative syntax is
really

  printf (<conversion-specifier> <arg>*)* $ <arg>*

which is (clearly a notch) simpler than the original

  printf (<string> <conversion-specifier> <arg>*)* <string> $ <arg>*
          ^^^^^^^^                                 ^^^^^^^^

I personally don't find the extra backticks very "noisy" and I prefer
the slightly simpler and more orthogonal design (as I already said).

In the below implementation of the alternative syntax, backtick is
implemented like other conversion specifiers. One could implement other
literal conversion specifiers. The below code is not completely polished.
I've been experimenting with various things like higher-order conversion
specifiers (e.g. L and PL below). The alternative approach has the
advantage that there is no need to manipulate dummy ""s.

  fun id x = x

  fun printf f = f id

  fun newCvt_0_1 toS m f =
      f (fn k => m (fn (g, s) => fn x1 => k (g, g (toS x1, s))))
  fun newCvt_1_0 toS m a1 f =
      f (fn k => m (fn (g, s) => k (g, g (toS a1, s))))
  fun newCvt_1_1 toS m a1 f =
      f (fn k => m (fn (g, s) => fn x1 => k (g, g (toS (a1, x1), s))))

  val $ = fn m => m (List.app print o rev o (fn (_, s) => s)) (op::, [])
(*Alternative "immediate" terminator:*)
(*val $ = fn m => m ignore (fn (s,_) => print s, ())*)

  val $$ = fn m => m (fn (_, s) => concat (rev s)) (op::, [])

  val ` = fn z => newCvt_1_0 id z

  val C = fn z => newCvt_0_1 Char.toString z
  val D = fn z => newCvt_0_1 Int.toString z
  val G = fn z => newCvt_0_1 Real.toString z
  val S = fn z => newCvt_0_1 id z

  fun L cvt =
      fn z =>
         newCvt_0_1
            let fun loop ss =
                    fn [] => "[]"
                     | [v] => concat ("["::rev ("]"::printf cvt$$v::ss))
                     | v::vs => loop (", "::printf cvt$$v::ss) vs
            in loop [] end
            z

  fun PL n cvt =
      fn z =>
         newCvt_0_1 (StringCvt.padLeft #" " n o printf cvt$$) z

  val () =
      (printf`"Hello.\n"$ ;
       printf`"An int "D`" and an int "D`".\n"$ 13 14 ;
       printf`"An padded int "(PL 5 D)`" and a padded real "(PL 5 G)`".\n"$ 13 3.1415 ;
       printf`"A real "G`" and a real "G`".\n"$ 13.1 3.1415 ;

       printf $ ;
       printf`"A string"`" - followed by another.\n"$ ;
       printf G C`"\n"$ 1.0 #"f" ;

       printf`"An empty "(L D)`" and a padded non-empty list "(L (PL 5 D))`".\n"$ [] [1, 2, 3] ;
       printf`"A list of padded lists of reals "(L (PL 10 (L G)))`".\n"$ [[1.0], [2.0, 3.0]])

As you can see, higher-order specifiers are parenthesized. It might also
be possible to implement some sort of lifting operator to allow nested
specs without requiring simple specs to be parenthesized, but it seems
considerably simpler to just use parentheses. I think I'll add a polished
implementation of this technique to my utility library with the most
common conversion specifiers (e.g. [1]).

The CPS+APP technique is probably going to be useful in other domains, too.
For example, it might allow one to implement a very Yacc like syntax (more so
than traditionally) for implementing (combinator) parsers without infix
operators.

-Vesa Karvonen

[1] http://caml.inria.fr/pub/docs/manual-ocaml/libref/Printf.html