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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With