Как преобразовать что-либо в строку, в SML?


Я пытаюсь реализовать тестовую функцию для сравнения и отображения сообщения об ошибке, если они не равны:

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 4
sml

3 ответа:

В Haskell вы бы сделали свой тип экземпляром класса Show и реализовали перегруженный вариант функции show :: Show a => a -> String, а затем напечатали show x вместо x. К сожалению, такого класса не существует в стандартном ML, и поэтому вы вынуждены писать свой собственный ненагруженный вариант show для каждого типа данных, который вы хотите распечатать.

Некоторые компиляторы SML (по крайней мере, московские ML) поддерживают перегруженную функцию makestring, которая работает только для подмножества встроенных типов и не какие-то сложные типы. Например, makestring 2 и makestring 2.0 оба работают, но makestring (0,0) нет.

Если вы хотите создать универсальную функцию утверждения, которая красиво выводит ошибку, вы можете создать тип данных с конструктором для каждого типа, значение которого вы хотите утверждать. Это будет работать как тип "union" в 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, "." ])
Это плохая замена для перегруженного человека.

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);

Итак

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

Отпечатки

Exception-
AssertionErrorException "test1 failed. actual: SOME [], expect: NONE"
   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 .
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, ")"]);

Фактически вы реализуете классы типов, описанные в предыдущем ответе.

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

Для получения дополнительной информации смотрите здесь