How to convert anything to string, in SML?

In Haskell, you would make your type an instance of the typeclass Show and implement an overloaded variant of the function show :: Show a => a -> String and then print show x rather than x. Unfortunately such a typeclass does not exist in Standard ML, and so you are forced to write your own non-overloaded variant of show for every datatype you want to pretty-print.

Some SML compilers (at least Moscow ML) support the overloaded function makestring that only works for a subset of built-in types and not any composite types. E.g. makestring 2 and makestring 2.0 both work, but makestring (0,0) does not. (Edit: David Matthews points out in an answer below that makestring in PolyML is better.)

If you wish to make a generic assertion function that pretty-prints the error, one thing you could do is create a datatype with a constructor for each type you wish to assert the value of. This would work like the "union" type in C.

exception AssertionError of string
datatype assert = AssertInt of int
                | AssertReal of real
                | AssertBoolBool of bool * bool
                | ...

fun assertPP (AssertInt i) = Int.toString i
  | assertPP (AssertReal r) = Real.toString r
  | assertPP (AssertBoolBool (b1,b2)) =
    String.concat ["(", Bool.toString b1, ", ", Bool.toString b2, ")" ]
  | assertPP (...) = ...

fun assert (testName, actual: assert, expect: assert) =
    actual = expect  (* ML infers equality for constructors *)
    orelse raise AssertionError (String.concat
        [ testName, " failed. actual: ", assertPP actual,
          ", expect: ", assertPP expect, "." ])

This is a poor man's replacement for overloading.


makestring was present in some of the early drafts of Standard ML but was removed before the final version. Poly/ML retained it as PolyML.makestring and this works on any type including structured types.

With this particular example it's possible to write

fun assert(testName, actual, expect) =
if actual = expect
   then true
   else raise AssertionErrorException(testName ^ " failed. actual: " ^
                PolyML.makestring actual ^ ", expect: " ^
                PolyML.makestring expect);

So

 assert("test1", SOME [], NONE);

prints

Exception-
AssertionErrorException "test1 failed. actual: SOME [], expect: NONE"
   raised

This happens to work because the type of actual and expect are equality types and this gives the compiler enough information to print the values properly. In general, though, if PolyML.makestring is included in a polymorphic function all that will be printed is "?". The solution is to pass in an extra parameter that is a function to convert the particular type to string.

fun assert(testName, actual, expect, toString) =
   if actual = expect
   then true
   else raise AssertionErrorException(testName ^ " failed. actual: " ^
                toString actual ^ ", expect: " ^ toString expect );

You then need to pass in a function that will convert the particular values into strings. In Poly/ML this can be PolyML.makestring.

assert("test2", (1,2,3), (1,2,4), PolyML.makestring);

prints

Exception-
   AssertionErrorException
  "test2 failed. actual: (1, 2, 3), expect: (1, 2, 4)" raised

If you're using a different SML implementation you could still do the same and pass in your own conversion function for the particular type.

assert("test2", (1,2,3), (1,2,4),
     fn (a,b,c) =>
        String.concat["(", Int.toString a, ",", Int.toString b,
                      ",", Int.toString c, ")"]);

In effect you are implementing the type classes described in the previous answer.


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

for more information look here

Tags:

Sml

Polyml