SML Hints

Note: This is not an ML tutorial! This is a collection of tips on using sml, the ML compiler we'll be using, and a short introductions to some helpful library functions. If you want general information on programming in ML, there's a lot of information available on the Web.

PostScript documentation for sml is also available. Documentation is available for the base, or standard, environment, the system-dependent library, and for sml's extended library. There's even an index. Be careful printing; some of this documentation is very long.

Getting started

We'll be using version .93 of the Standard ML of New Jersey compiler, which is installed in /s/std/bin. There are three ML compilers installed there, which differ by which libraries they have preloaded. I recommend you use /s/std/bin/eXene; the other two executables, sml and cml, don't have all the libraries you'll need preloaded.

Here's what a typical session looks like

ammons@diamond-joe:cs538> eXene
eXene -- version 0.4 -- February 11, 1993
Concurrent ML -- version 0.9.8 -- February 1, 1993
Standard ML of New Jersey, Version 0.93, February 15, 1993
val it = () : unit
- output (std_out, "Hello, world.\n");
Hello, world.
val it = () : unit
- ammons@diamond-joe:cs538> 
I typed the stuff after the - prompts. The rest is output from the compiler. I got out of eXene by pressing Ctrl-D.

There is a later version of sml, sml-1.08, installed under /unsup. Don't use this version; it is unstable and somewhat incompatible with earlier versions of sml.

Interacting with sml

The previous example illustrated that sml is an interactive compiler. You work with it by typing expressions at the prompt. sml compiles the expressions you type, evaluates them, and prints the answer.

Typing expressions at the prompt is a great way to test code and develop small functions, but if you're writing something larger, you'll probably want to put it in a file and import the file. Say that hello.sml is

output (std_out, "Hello, world.\n");
Then, the following session is equivalent to the session above:
ammons@diamond-joe:cs538> eXene
eXene -- version 0.4 -- February 11, 1993
Concurrent ML -- version 0.9.8 -- February 1, 1993
Standard ML of New Jersey, Version 0.93, February 15, 1993
val it = () : unit
- use "hello.sml";
[opening hello.sml]
Hello, world.
val it = () : unit
val it = () : unit
- ammons@diamond-joe:cs538> 
The compiler evaluated the expression from hello.sml just as if it had been typed at the prompt. This example only had one expression in the file, but the file may contain several expressions. The expressions are evaluated in the order in which they appear in the file.

Making an executable

We've seen how to use sml interactively. You can also use sml to produce standalone programs. The first step is to define a function that you want to be called when your executable starts up. Returning to the hello example,
ammons@diamond-joe:cs538> eXene
eXene -- version 0.4 -- February 11, 1993
Concurrent ML -- version 0.9.8 -- February 1, 1993
Standard ML of New Jersey, Version 0.93, February 15, 1993
val it = () : unit
- fun hello (argv, environ) = output (std_out, "Hello, world.\n");
val hello = fn : 'a * 'b -> unit
- exportFn ("hello", hello);

[Major collection... 15% used (692596/4413684), 284 msec]

[Decreasing heap to 3397k]

[Major collection... 44% used (310940/695088), 83 msec]

[Decreasing heap to 1521k]
ammons@diamond-joe:cs538> ls -l hello
-rwxr-xr-x   1 ammons   26014     417436 Apr  5 06:54 hello
ammons@diamond-joe:cs538> ./hello
Hello, world.
ammons@diamond-joe:cs538> 
The function exportFn (s, f) writes an executable s that starts by executing f. f must take a tuple of 2 lists of strings. The first list is the list of command line arguments and the second list is a list of environment variables. Here's a program which actually uses the command line arguments:
ammons@diamond-joe:cs538> eXene
eXene -- version 0.4 -- February 11, 1993
Concurrent ML -- version 0.9.8 -- February 1, 1993
Standard ML of New Jersey, Version 0.93, February 15, 1993
val it = () : unit
- fun paste (nil) = "\n"
=   | paste (s::nil) = s ^ paste (nil)
=   | paste (s::tl) = s ^ " " ^ paste (tl);
val paste = fn : string list -> string
- fun echo (argv, environ) = output (std_out, paste argv);
val echo = fn : string list * 'a -> unit
- exportFn ("echo", echo);

[Major collection... 8% used (358796/4429500), 183 msec]

[Decreasing heap to 1753k]

[Major collection... 99% used (358908/358988), 150 msec]
ammons@diamond-joe:cs538> ./echo a bunch of args
./echo a bunch of args
ammons@diamond-joe:cs538> 
Notice that the first element of argv is the name of the program, just like in C.

There's no reason to use exportFn before you're ready to produce your final executable. It's much more convenient to test your program by evaluating expressions at the sml prompt.

Sundries

Your maze programs will use library functions to convert strings to numbers and to generate random numbers. Also, if you are not going for the functional programming bonus, you might want to use updateable arrays. To save you some aggravation, I've put together some examples which use these features. For details, you should also read the documentation.

Strings to numbers

The function atoi converts strings to integers.
ammons@diamond-joe:cs538> eXene
eXene -- version 0.4 -- February 11, 1993
Concurrent ML -- version 0.9.8 -- February 1, 1993
Standard ML of New Jersey, Version 0.93, February 15, 1993
val it = () : unit
- open StringCvt;
open StringCvt
exception Convert = Convert
val strToInt = fn : radix -> string * int -> int * int
val atoi = fn : string -> int
val xatoi = fn : string -> int
val oatoi = fn : string -> int
val strToReal = fn : string * int -> real * int
val atof = fn : string -> real
val strToBool = fn : string * int -> bool * int
val atob = fn : string -> bool
- atoi "14";
val it = 14 : int
- 
I opened StringCvt so I could refer to StringCvt.atoi simply as atoi.

These functions are documented in /s/sml/doc/sml/manual/LIB.ps.

Random numbers

The Random structure contains functions to generate and manipulate random numbers. Like rand (), the random number generator needs to be seeded, so you'll also want to learn about the gettimeofday function. Here's a simple example:
- open System.Timer; (* for the time datatype *)
...
- open System.Unsafe.CInterface; (* for gettimeofday *)
...
val gettimeofday = fn : unit -> time
- open Random;
val random = fn : real -> real
val mkRandom = fn : real -> unit -> real
val norm = fn : real -> real
val range = fn : int * int -> real -> int
- gettimeofday ();
val it = TIME {sec=828719866,usec=910014} : time
- val TIME { sec=s, ... } = gettimeofday ();
val s = 828719870 : int
- val gen = mkRandom (real s);
val gen = fn : unit -> real
- gen ();
val it = 1863404295.0 : real
- gen ();
val it = 1481961864.0 : real
- val tosix = range (1, 6);
val tosix = fn : real -> int
- val roll = tosix o gen;
val roll = fn : unit -> int
- roll ();
val it = 6 : int
- roll ();
val it = 1 : int
- roll ();
val it = 2 : int
As you can see, roll () is not a pure function, because, given identical input, it doesn't always return identical values. Despite this, programs that use Random or gettimeofday are still eligible for the functional programming bonus.

These functions are documented in /s/sml/doc/sml/manual/LIB.ps.

Arrays

Programs that use the Array structure, on the other hand, aren't eligible for the bonus. The array structure provides functions to manipulate updateable arrays of values, sort of like C arrays.
- open Array;
open Array
exception Subscript = Subscript
val tabulate = fn : int * (int -> '1a) -> '1a array
val length =  : 'a array -> int
val sub =  : 'a array * int -> 'a
val arrayoflist = fn : '1a list -> '1a array
val array = fn : int * '1a -> '1a array
exception Size = Size
val update =  : 'a array * int * 'a -> unit
- val iarray = array (100, 4);
val iarray = [|4,4,4,4,4,4,4,4,4,4,4,4,...|] : int array
- val rarray = arrayoflist ([1.3,3.14,2.7]);
val rarray = [|1.3,3.14,2.7|] : real array
- sub (iarray,12);
val it = 4 : int
- sub (iarray, 1000);

uncaught exception Subscript
- sub (rarray, 1);
val it = 3.14 : real
- update (rarray, 1, ~3.14159);
val it = () : unit
- sub (rarray, 1);
val it = ~3.14159 : real
- length rarray;
val it = 3 : int
- length iarray;
val it = 100 : int
- 
As the example demonstrates, you look up entries in the array with sub and change the value of an entry with update.

If you are going for the functional programming bonus and need a constant-time indexable sequence of values, take a look at the Vector structure. That structure contains functions to create read-only "arrays". You specify the initial contents, which cannot subsequently be changed.

These functions are documented in /s/sml/doc/sml/manual/BASE.ps.

More information

Professor Larus has gathered some links to information on ML. I particularly recommend working through the Gentle Introduction to ML.