Please send all questions & assignments to:
dsolarek@eng.utoledo.edu

 

Standard ML (SML)

Structures and Signatures

The Standard ML Basis Library

The draft version of the SML Basis Library provides details of the structures and signatures available in SML. The latest version of Standard ML of New Jersey (as well as Harlequin's MLWorks and Moscow ML) implement significant portions of this library.

Use this library as a reference when you are writing SML/NJ programs. It will provide with the syntax needed to access all of the built-in functions and features of the language.

The examples found on this page were created with the aid of the Basis library.

Using Signatures

The examples below illustrate the use of object-oriented notation to access the various built-in functions available in the SML/NJ language. As you work through each of these examples, remember to press the Enter key () after typing the semicolon (;) that terminates each SML expression. For example, here is the absolute value function which is part of the INTEGER signature:
 - Int.abs(~34);
 val it = 34 : int
As the example above illustrates, the INTEGER signature is accessed by using Int. as a prefix for the abs function. Remember, SML is case sensative, so int. is not the same as Int..
 - Int.max(23,78);
 val it = 78 : int
 - Int.toString(65);
 val it = "65" : string
 - Int.maxInt;
 val it = SOME 1073741823 : int option
 - Int.precision;
 val it = SOME 31 : int option
The Int structure contains many arithmetic operators. Some of these, such as Int.quot and Int.rem, are subtle variants on more familiar operators such as div and mod. Quotient and remainder differ from divisor and modulus in their behaviour with negative operands. Other operations on integers include Int.abs and Int.min and Int.max, which behave as expected. This structure also provides conversions to and from strings, named Int.toString and Int.fromString, of course. An additional formatting function provides the ability to represent integers in bases other than decimal. The chosen base is specified using a type which is defined in another library structure, the string convertors structure, StringCvt. This permits very convenient formatting of integers in binary, octal, decimal or hexadecimal, as illustrated below.
 Int.fmt StringCvt.BIN 1024 = "10000000000" 
 Int.fmt StringCvt.OCT 1024 = "2000" 
 Int.fmt StringCvt.DEC 1024 = "1024" 
 Int.fmt StringCvt.HEX 1024 = "400" 
Each of these can be verified by using the following syntax:
 - Int.fmt StringCvt.BIN 1024;
 val it = "10000000000" : string
The Int structure may define largest and smallest integer values. This is not to say that structures may arbitrarily shrink or grow in size, Int.maxInt either takes the value NONE or SOME i, with i being an integer value. Int.minInt has the same type.

The SML MATH signature specifies basic mathematical constants, the square root function, and trigonometric, hyperbolic, exponential and logarithmic functions (all based on the real data type). These functions have roughly the same semantics as their counterparts in math.h from ISO C. The following examples illustrate the use of some common built-in functions from the MATH signature:

 - Math.pi;
 val it = 3.14159265359 : real
 - Math.sqrt(it);
 val it = 1.77245385091 : real
 - Math.sin(it);
 val it = 0.979735932476 : real
 - Math.e;
 val it = 2.71828182846 : real
When using any of the SML built-in functions, it is important to make certain that your arguments are of the correct type. For example:
 - Math.pow(2,6);
 stdIn:21.1-21.14 Error: operator and operand don't agree
   operator domain: real * real
   operand:         int * int
   in expression:
     Math.pow (2,6)
 - Math.pow(2.0,6.0);
 val it = 64.0 : real

Polymorphism

Polymorphism allows us to write generic functions (in some cases). This means that the types need not be fixed.
 - fun I x = x;
 val I = fn : 'a -> 'a
 - I 4;
 val it = 4 : int
 - I 2.3;
 val it = 2.3 : real
 - I "hello";
 val it = "hello" : string
Use of type-specific atomic operators obviously restricts polymorphism:
 fun doublenegate x = not (not x);
 val doublenegate = fn : bool -> bool
It also screws things up when it is combined with overloading: there is too little type information to tell if int or real is meant, and the type inference algorithm has to pick one or the other.
 - fun double x = x + x;
 val double = fn : int -> int
It picks int, if you meant real you will have to explicitly give that type. Here is an example of how you declare a type for a function's argument(s):
 fun double x : real = x + x; 
 val double = fn : real -> real

Pattern Matching

Standard ML provides a mechanism whereby the notation which introduces the function parameter may constrain the type or value of the parameter by requiring the parameter to match a given pattern (so-called "pattern matching"). The basic syntax is:
 fun identifier ( first pattern ) = first expression
  |   identifier ( second pattern ) = second expression
    .
    .
    .
  |   identifier ( last pattern ) = last expression;
the identifiers must all be the same; a variable may appear just once in a pattern

Defining a function by cases. The following function, day, maps integers to strings:

 - val day = fn 0 => "Monday"
 =   | 1 => "Tuesday"
 =   | 2 => "Wednesday"
 =   | 3 => "Thursday"
 =   | 4 => "Friday"
 =   | 5 => "Saturday"
 =   | _ => "Sunday";
 val day = fn : int -> string
 - day 4;
 val it = "Friday" : string
 - day(2);
 val it = "Wednesday" : string
The following example shows an even simpler pattern-dependent expression:
 - val (a,b) = (2,"two");
 val a = 2 : int
 val b = "two" : string
Or-patterns. SML/NJ has extended the syntax of patterns to allow "or-patterns." The basic syntax is:
 (apat1 | ... | apatn)
where the apati are atomic patterns and the pipe character (|) can be read as "OR". The other restriction is that the variables bound in each apati must be the same, and have the same type. A simple example is:
 fun f ("y"|"yes") = true
   | f _ = false;
which has the same meaning as:
 fun f "y" = true
   | f "yes" = true
   | f _ = false;
In both examples, the underscore character ( _ ) is used to represent "any other" character.

Recursion

Recursion is when a function repeatedly calls itself, until some terminating condition is reached. The following example illustrates the use of a recursive function to sum the integers from 1 to n. In this functional definition the function declaration is marked as being recursive by using the Standard ML keyword rec. The only values which can be defined recursively in Standard ML are functions.
 - val rec sum' = fn 1 => 1
 = | n => sum' (n - 1) + n;
 val sum' = fn : int -> int
 - sum' 5;
 val it = 15 : int
 - sum' 10;
 val it = 55 : int

Vectors and Arrays

The SML Basis Library defines both polymorphic and monomorphic arrays and vectors. Array elements can be changed, while vectors elements are fixed. Both arrays and vectors are indexed from zero.

The Vector structure provides polymorphic immutable sequences. Here are several examples of how to use the Vector structure.

Creating a simple vector using #:

 - val d = #[2,2,3,5,6,88,99];
 val d = #[2,2,3,5,6,88,99] : int vector
 - Vector.sub(d,5);
 val it = 88 : int
Creating a vector from a list.
 - [1,2,2,3,4,7,8,9,12];
 val it = [1,2,2,3,4,7,8,9,12] : int list
 - val c = Vector.fromList it;
 val c = #[1,2,2,3,4,7,8,9,12] : int vector
 - Vector.maxLen;
 val it = 33554431 : int
 - Vector.length c;
 val it = 9 : int
 - Vector.sub (c,7);
 val it = 9 : int
Creating a vector using a function:
 - fun f x = x*x;
 val f = fn : int -> int
 - val w = Vector.tabulate(12,f);
 val w = #[0,1,4,9,16,25,36,49,64,81,100,121] : int vector
Creating a new vector using the map function:
 - fun f x = x*x;
 val f = fn : int -> int
 - Vector.map f d;
 val it = #[4,4,9,25,36,7744,9801] : int vector
The Array structure provides polymorphic mutable sequences. Use of the Array structure is illustrated below:
 - val a = Array.array(10,0);
 val a = [|0,0,0,0,0,0,0,0,0,0|] : int array
 - Array.length a;
 val it = 10 : int
 - Array.update(a,2,14);
 val it = () : unit
 - a;
 val it = [|0,0,14,0,0,0,0,0,0,0|] : int array
 - Array.sub(a,2);
 val it = 14 : int
The Array2 structure provides polymorphic mutable 2-dimensional arrays. Arrays also have a special equality property: two arrays are equal if they are the same array, i.e., created by the same call to a primitive array constructor such as array, fromList, etc.; otherwise they are not equal. This also holds for arrays of zero length.

 

Assignment

Complete the following assignment before our next meeting:
  1. Create an SML/NJ program which reads up to nine (9) single-digit numbers from a data file and loads them into an array (or vector).
  2. Your program should also sort these numbers into numeric order and print the result to the screen.
  3. If you want to "get fancy" figure out how to write the sorted array/vector to a data file.

    Click on the button at left to return to the calling page.

 

There have been visitors since 11/26/2003

Added to the Web: November 30, 1999.
Last updated: December 1, 1999.
Web page design by Dan Solarek.

http://cset.sp.utoledo.edu/cset4250/