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

10

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

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)

如何解决这个问题?
3个回答

9

makestring曾经出现在Standard 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);

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

打印

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

这种方法有效是因为actualexpect的类型是相等的,这足以让编译器打印出正确的值。然而,通常情况下,如果在多态函数中包含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

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

打印

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, ")"]);

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

8
在Haskell中,您需要将类型设置为类型类“Show”的实例,并实现函数“show :: Show a => a -> String”的重载版本,然后打印“show x”而不是“x”。不幸的是,在标准ML中不存在这样的类型类,因此您必须为每个要漂亮地打印的数据类型编写自己的非重载版本的“show”。
一些SML编译器(至少Moscow ML)支持重载函数“makestring”,但仅适用于内置类型的子集,而不适用于任何复合类型。例如,“makestring 2”和“makestring 2.0”都可以使用,但“makestring(0,0)”则不行。(编辑: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, "." ])

这是一种替代重载的简易方法,适用于资金有限的人。

5
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

更多信息请查看这里

该链接提供了有关IT技术的更多信息。

谢谢,但我不想打印出来,只想返回一个字符串,以便在我的错误消息中使用。 - Freewind
1
也许不是问题的答案,但绝对非常有帮助。一个小提示:为了使其编译,您必须提供某个身份函数:“fun id x = x”。 - gruenewa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接