Sml Language

Standard ML (or SML for short) is a modern dialect of ML [Ml Language].

Type-based pattern matching and function tricks make this a handy language for writing compilers.

Also see Ml Kit, Ml Ton, and Sml Nj Language. And Sml Unit.


How about some programming examples? I'll start small and simple:

(* A comment in SML. (* They can be nested. *) *)

(* This function recurses along a list, applying a function to each element and an accumulator in turn. Note the pattern matching and the use of _, which matches (and ignores) anything. *)

fun revfold _ nil b = b (* If the second argument is nil (an empty list), return the third argument. *) | revfold f (hd::tl) b = revfold f tl (f(hd,b)); (* Otherwise, recurse on the tail of the list and replace b with the result of applying f to the head of the list and the old b. *)

(* More pattern matching, this time to destructure the list (the second argument) into a head (hd) and a tail (tl). The head is a single list element, the tail is the rest of the list (which may be nil). *)

(* The type of this function is "val revfold = fn :
('a * 'b -> 'b) -> 'a list -> 'b -> 'b".

This means that the first argument is a function of a two-element tuple of incomplete types 'a and 'b which may or may not be the same. The function must return a value of type 'b, however. The second argument is a list of elements of type 'a, and the third argument is a single object of type 'b. The return value is also a single object of type 'b. *)

(* Filter out undesirable elements from a list. *)

fun filter _ nil = nil | filter f (hd::tl) = if (f hd) then hd::(filter f tl) else (filter f tl);

(* Another basic recursive function. The if..then..else works exactly as expected. The ::
infix operator

is cons from Lisp:
It glues individual objects into lists. The list [1,2,3] can also be written

1::2::3::nil, just like the Lisp list '(1 2 3) can also be written '(1 . (2 . (3 . nil))). *)

(* Check to see if a value is in a specific range. *) (* This function takes two arguments: A two-element tuple of ints and a single int. *)

fun inRange (min,max) v = ((v >= min) andalso (v <= max));

(* Determine if a given character is a decimal digit. *) (* The o operator is the composition operator: It works such that f o g is equivalent to f(g(x)) for some implicit x. Here, it allows us to trivially convert the argument from a char to an int before passing it to the anonymous function of one argument created by just passing inRange the tuple. *)

val digitp = inRange (Char.ord #"0", Char.ord #"9") o Char.ord;

(* The classic atoi function. The first argument to revfold is what anonymous functions look like. Char.ord converts characters into ints and explode turns strings into lists of characters. *)

fun atoi s = revfold (fn(a,b)=>((Char.ord a)-(Char.ord #"0")+10*b) (filter digitp (explode s)) 0;

(* The inevitable factorial function. Note the two special cases handled by pattern-matching. *)

fun fac 0 = 0 | fac 1 = 1 | fac n = if n < 0 then n else n*(fac(n-1));

(* Compute the factorial of the number represented by the string and return the result as a string. *)

fun strfac s = Int.toString(fac(atoi s)); (* Int.toString is a standard library function. *)

(* Determine if the string really is a valid number. Useful for error reporting. *) (* REALLY useful for showing off let..in..end blocks and lexical functions. ;) *)

fun check "" = true | check s = let fun inner nil = true | inner (hd::tl) = digitp hd andalso inner tl in inner (explode s) end;

(* I do it this way instead of with revfold because this function will bail early if the string is not valid. *)

(* The main function. Note how the 'val _ =' means the return value of the main loop is thrown away. *) (* The function app is like map in that it maps its first (function) argument across its second (list), but it does not return a (useful) value. It is only useful if the function has side-effects. Such as printing something. Like this one does. *)

val _ = app (fn s => (if not (check s) then (print s; print " is not a number\n") else (print s; print "\t"; print(strfac s); print "\n"))) (CommandLine.arguments());

(* Command Line.arguments is a function of one argument:
(), also called unit. A function of no arguments

is impossible in SML:
Attempting to call a function without any arguments gets you the value of that

function, just as calling a function with 'too few' arguments creates an anonymous function that will take the arguments you did not specify. Similarly, it is impossible for a function to not return a value. The solution is unit, which is how SML programmers tell each other (and their compilers) that this function either takes no real arguments or returns no real value. It is like NULL in C, except type-safe and otherwise completely unlike any other value in the language. *)

(* Save this source in a file called "fac.sml", compile it with MLton, and run it as "./fac 0 1 10 12". (12! is the highest factorial a 32-bit machine can handle. SML programs treat integer overflow as a failure and this program will fail with an unhandled exception if you attempt to compute a factorial larger than you can handle.) *)


Sorry -- I just had to space out the code above -- I just couldn't read it otherwise. Hopefully I didn't accidentally change the semantics of the program. Not sure if SML is white-space sensitive or not like Haskell is. --Samuel Falvo


Um... 0! = 1, not 0. And isn't unit more like void than NULL?


There is a wiki discussing an improved SML at successor-ml.org


See original on c2.com