Search code examples
smlpolyml

How to convert anything to string, in SML?


I'm trying to implement a test function to compare and show error message if they are not equal:

exception AssertionErrorException of string

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

Unfortunately, it doesn't work if I invoke it with non-string parameters:

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

It can't be compiled, and the error message is:

Error: operator and operand don't agree [tycon mismatch]
  operator domain: string * string * string
  operand:         string * 'Z list option * 'Y option
  in expression:
    assert ("test1",SOME nil,NONE)

How to fix it?


Solution

  • 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.