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
and compile it (for example using the mlton compiler).
Calling it like
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
if called with a negative parameter.
This exception is handled by printing the
"Undefined for negative numbers." message.
Empty exception is raised by
if the argument list is empty.
Note that SML exception are not checked like Java exceptions,
so they pass right through
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
My opinion about the various
IntInf occurences is divided.
They make SML use integers of infinite precision,
instead of using
int and raising an
with bigger factorials.
On the one hand they seem unnecessary
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.