I learned a little SML recently. The SML programming language is special and interesting for the following reasons:

Unfortunately there are some downsides as well, because it is not a main­stream language. Little documentation, few users, marginal tool support, many different compilers (though they are pretty much inter­change­able unlike Scheme interpreters) and quite academic tutorials make it hard to learn SML. Maybe documenting my first steps helps other interested people.

The first program was HelloWorld, but after some playing around the following code remained, which implements a factorial command line program:

(* simple factorial function defined with IntInf for big number support *)
exception Undefined;
fun factorial n:IntInf.int =
    if n<0 then raise Undefined else
        if n<2 then 1 else n * (factorial (n - 1));

(* factorial function on strings *)
fun str_fac n =
    case (IntInf.fromString n)
        of NONE => "Argument is not a number."
         | SOME m => n ^ "! = " ^ (IntInf.toString (factorial m));

(* calculate factorial of first argument or error message *)
val msg = str_fac (hd (CommandLine.arguments ()))
    handle Undefined => "Undefined for negative numbers."
         | Empty => "Need a number as an argument.";

print (msg ^ "\n");

Save that code as factorial.sml and compile it (for example using the mlton compiler). Calling it like ./factorial 5 will output 5! = 120. It will also output various error messages, if it is not treated correctly. The binary is very fast with mlton and it seems to optimize the (naive) factorial function into a loop. At least big numbers do not produce a stack overflow.

The factorial function will raise an Undefined exception, if called with a negative parameter. This exception is handled by printing the "Undefined for negative numbers." message. The Empty exception is raised by hd, if the argument list is empty. Note that SML exception are not checked like Java exceptions, so they pass right through str_fac, which is a Good Thing™. I particularly like the expression-based syntax for exception handling in SML, which is more concise than try/throw/catch statements.

The case that the argument can not be parsed as an integer is handled via the Option structure (equivalent to the Maybe Monad in Haskell). This is an alternative to exceptions, which enforces the programmer to think about the failure case. With pattern matching SML provides an elegant syntax to handle the two cases NONE and SOME m.

My opinion about the various IntInf occurences is divided. They make SML use integers of infinite precision, instead of using int and raising an Overflow exception with bigger factorials. On the one hand they seem unnecessary and using IntInf instead of int seems to be a better default in my eyes. On the other hand the programmer has to think about the tradeoff now and the type inference makes it tolerable.

All in all I am pleased with SML so far. The code is concise and the type checker makes me think about the corner cases.

© 2010-01-17