Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert anything to string, in SML?

Tags:

sml

polyml

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?

like image 489
Freewind Avatar asked Oct 13 '13 09:10

Freewind


3 Answers

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.

like image 189
sshine Avatar answered Nov 04 '22 14:11

sshine


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.

like image 40
David Matthews Avatar answered Nov 04 '22 15:11

David Matthews


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

like image 5
Alexander Kobelev Avatar answered Nov 04 '22 16:11

Alexander Kobelev