I learned a little SML recently. The SML programming language is special and interesting for the following reasons:
- It compiles to fast executables (unlike Python, Lisp, Ruby)
- It is strongly typed (unlike C, C++)
- It does not need a VM (no startup overhead unlike Java, C#)
- It is minimalistic (unlike OCaML)
- It is strictly evaluated (unlike Haskell)
- It has formal semantics
- It has an advanced module system (though not perfect)
Unfortunately there are some downsides as well, because it is not a mainstream language. Little documentation, few users, marginal tool support, many different compilers (though they are pretty much interchangeable 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.