如何在SML中将任何内容转换为字符串?

如何在SML中将任何内容转换为字符串?,sml,polyml,Sml,Polyml,我正在尝试实现一个测试函数,以便在它们不相等时比较并显示错误消息: exception AssertionErrorException of string fun assert(testName, actual, expect) : bool = if actual = expect then true else raise (AssertionErrorException (testName ^ " failed. actual: " ^ actual

我正在尝试实现一个测试函数,以便在它们不相等时比较并显示错误消息:

exception AssertionErrorException of string

fun assert(testName, actual, expect) : bool =
    if actual = expect
    then true
    else raise (AssertionErrorException (testName ^ " failed. actual: " ^ actual 
                ^ ", expect: " ^ expect ));
不幸的是,如果我使用非字符串参数调用它,它将不起作用:

assert("test1", SOME [], NONE);
无法编译,错误消息为:

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)
如何修复它

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
下面是一个使用示例

val () = printf `"Int="I`"  Bool="B`"  Real="R`"\n" $ 1 false 2.0
这将打印以下内容

Int=1  Bool=false  Real=2.0

有关更多信息,请查看Haskell中的,您可以将您的类型作为typeclass
Show
的实例,并实现函数
Show::Show a=>a->String
的重载变量,然后打印
Show x
,而不是
x
。不幸的是,这样的typeclass在标准ML中并不存在,因此您必须为您想要打印的每个数据类型编写自己的非重载变量
show

一些SML编译器(至少是SML)支持重载函数
makestring
,该函数仅适用于内置类型的子集,而不适用于任何复合类型。例如,
makestring 2
makestring 2.0
都可以工作,但是
makestring(0,0)
不能。(Edit:David Matthews在下面的回答中指出,PolyML中的
makestring
更好。)

如果您希望生成一个能够漂亮地打印错误的通用断言函数,那么您可以做的一件事是为您希望断言其值的每个类型创建一个带有构造函数的数据类型。这与C中的“union”类型类似

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, "." ])
这是一个穷人对重载的替代品。

makestring出现在标准ML的一些早期草案中,但在最终版本之前被删除。Poly/ML将其保留为PolyML.makestring,这适用于任何类型,包括结构化类型

有了这个特殊的例子,就可以编写

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

印刷品

Exception-
AssertionErrorException "test1 failed. actual: SOME [], expect: NONE"
   raised
Exception-
   AssertionErrorException
  "test2 failed. actual: (1, 2, 3), expect: (1, 2, 4)" raised
这是因为actual和expect的类型是相等类型,这为编译器提供了足够的信息来正确打印值。不过,一般来说,如果PolyML.makestring包含在多态函数中,那么将打印的所有内容都是“?”。解决方案是传入一个额外的参数,该参数是将特定类型转换为字符串的函数

fun assert(testName, actual, expect, toString) =
   if actual = expect
   then true
   else raise AssertionErrorException(testName ^ " failed. actual: " ^
                toString actual ^ ", expect: " ^ toString expect );
然后需要传入一个函数,该函数将特定值转换为字符串。在Poly/ML中,可以是PolyML.makestring

印刷品

Exception-
AssertionErrorException "test1 failed. actual: SOME [], expect: NONE"
   raised
Exception-
   AssertionErrorException
  "test2 failed. actual: (1, 2, 3), expect: (1, 2, 4)" raised
如果您使用的是不同的SML实现,您仍然可以执行相同的操作,并为特定类型传入您自己的转换函数

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

实际上,您正在实现上一个答案中描述的类型类。

谢谢,但我不想打印出来,只想返回一个字符串,我可以在错误消息中使用它,可能不是问题的答案,但肯定非常有用。一个小提示:要使其编译,您必须在某处提供标识函数:“fun id x=x”