SML synopsis III

This document describes a simple subset of the SML language. A more simple document SML syntax I highlights the most basic constructs. This document describes in more detail some of the basic constructs of the language and in addition this document describes the top-level interaction with the user, user-defined datatypes, real numbers, and lists. The BNF descriptions of the syntactic categories are linked, so that the reader can follow the relationship between syntactic units. Every syntactic category is illustrated by an example.

Index of syntactic categories

Keyword index

Phrase

The syntactic category phrase is the category of all things that can be typed to the SML system at the top-level, i.e., in response to the prompt.

Three different types of phrases are expected at the top-level: expressions, declarations, and directives.

<phrase> ::= <expression> ;
<phrase> ::= <declaration> ;
<phrase> ::= <directive> ;

Expression

The syntactic category expression is the category of all things that denote values in SML.
<expression> ::= <optional op> <identifier>               (object name)
<expression> ::= <optional op> <identifier>               (constructor name)
<expression> ::= <optional op> <identifier>               (exception name)

<expression> ::= <literal>

<expression> ::= <record expression>
<expression> ::= # <label>                               (record selection)
<expression> ::= ( )                                     (unit value)
<expression> ::= ( <expression> , ... , <expression> )   (tuple)
<expression> ::= [ <expression> , ... , <expression> ]   (list)
<expression> ::= ( <expression> ; ... ; <expression> )   (sequence)

<expression> ::= while <expression> do <match>            (iteration)

<expression> ::= <expression> <identifier> <expression>   (infix application)
<expression> ::= <expression> <expression>                (function application)
<expression> ::= fn <match>                               (anonymous function)

<expression> ::= case <expression> of <match>             (case analysis)

<expression> ::=
    if <expression> then <expression> else <expression>  (cond. expression)
<expression> ::= <expression> andthen <expression>       (conjunction)
<expression> ::= <expression> orelse <expression>        (disjunction)

<expression> ::= <expression> handle <match>              (handle exception)
<expression> ::= raise <expression>                       (raise exception)

<expression> ::= let <declaration> in <expression> end   (let expression)

<expression> ::= <expression> : <type>                    (typed expression)
<expression> ::= ( <expression> )                        (parenthesized)

Pattern

The syntactic category pattern is the category describing constructed values (and exceptions). Patterns are used in matching against values to establish bindings of identifiers. Apart from a few special patterns, this category roughly paralles that of expressions.
<pattern> ::= _                                       (wildcard)

<pattern> ::= <optional op> <identifier>               (object name)
<pattern> ::= <optional op> <identifier>               (constructor name)
<pattern> ::= <optional op> <identifier>               (exception name)

<pattern> ::= <literal>

<pattern> ::= <record pattern>
<pattern> ::= ( )                                     (unit value)
<pattern> ::= ( <pattern> , ... , <pattern> )         (tuple)
<pattern> ::= [ <pattern> , ... , <pattern> ]         (list)

<pattern> ::= <pattern> <identifier> <pattern>         (infix value constr.)
<pattern> ::= <pattern> <identifier> <pattern>         (infix exception constr.)
<pattern> ::= <optional op> <identifier> <pattern>     (value construction)
<pattern> ::= <optional op> <identifier> <pattern>     (value construction)

<pattern> ::= <pattern> : <type>                       (typed pattern)
<pattern> ::= <optional op> <identifier> <optional type> as <pattern>       (layered pattern)
<pattern> ::= ( <pattern> )                           (parenthesized)

Record

<expression row> ::= { label = <expression> }

Optional op

<optional op> ::= op
<optional op> ::=

Declaration

The syntactic category declaration is the category of all things that bind values to names in SML. Besides objects (and functions), and datatypes, exceptions are given names with declarations. The scope of these bindings can be controlled using the local declaration.

<declaration> ::= val <identifier> = <expression>         (value binding)
<declaration> ::= exception <identifier>                  (exception binding)
<declaration> ::= fun <identifier> <identifier> = <expression>   (function binding)
<declaration> ::=
    datatype <identifier> = <constr. binding> | ... | <constr. binding>   (type binding)
<declaration> ::= local <declaration> in <declaration> end       (local declaration)

Type Binding

The syntax of a type binding is
datatype <identifier> = <constructor binding> | ... | <constructor binding>
where any number of constructor bindings separated by | are permitted. The construct introduces a new data structure. For example,
datatype direction = up | down
introduces a new datatype called direction. An element of the datatype can be created using a constructor introduced in the one of the constructor bindings.

Constructor Binding

A constructor binding introduces the name of a new value that constructs a particular data structure. The first form below is a null-ary constructor, a constructor that constructs an element of the data structure without any arguments.
<constructor binding> ::= <constructor name>
<constructor binding> ::= <constructor name> of <type>

Type

<type> ::= int
<type> ::= bool
<type> ::= real
<type> ::= string
<type> ::= unit
<type> ::= <type> list
<type> ::= <type> -> <type>
<type> ::= <type> * ... * <type>

Object Name

Identifiers can be used to name objects. For example, if the identifier a has been given the value 3, then the expression a denotes that value. The identifier a may have been bound to the value in a let construct:
let
    val a = 1+2;
in
    a+a  (* "a" denotes the value 3 here *)
end
The same effect can be achieved in a dialog with the SML system:
- val a = 1+2;
val a = 3 : int
- a+a;  (* "a" denotes the value 3 here *)
val it = 6 : int

Another way an identifier can refer to an object is if it is the formal parameter of a function. For example, the x in the body of the function fun f (x) = x+x; stands for the value 3 when the function f is applied to the expression 1+2.

Constructor Name

The names of constructors are syntactically identical to the names of objects. The names of both are ordinary identifiers, strings of alphabetic characters, for the most part. Certain objects, those that construct data structures, are distinguished from other objects, especially in conjunction with patterns.

Constructor names are defined in type bindings and are used anywhere value names are permitted. For example, the following type binding declares three constructor names.

- datatype color = red | green | blue;
datatype  color
  con blue : color
  con green : color
  con red : color
- red;
val it = red : color
Observe, that the SML system recognizes the constructors and prints con instead of val. It is not possible in introduce a constructor in a value binding; only in a type binding.

Literal

The value of some objects is based solely on their syntactic structure.
<literal> ::= <string literal>
<literal> ::= <integer literal>
<literal> ::= <real literal>
<literal> ::= <unit literal>
We do not include tuples and functions here as they are important enough to get their own syntactic category. There are two predefined constructors of the predefined type bool. They are true and false.

Anonymous Function

The syntax of a function definition is:
fn <identifier> => <expression>
We call the function anonymous because it does not have a name. For example, the integer successor function is:
fn n => n+1
The identifier n is the name we give to the argument to the function. The function itself does not have a name. Here is another example:
fn s => s^s           (* "^" is predefined; it concatenates two strings *)
Most of the time we want the functions we define to have names. A name can be bound to any object using the value binding construct.
val succ = fn n => n+1
val double = fn s => s^s
This combination of a value binding and a function definition is so common it has its own special syntax (see function bindings).

We define functions in order to apply then to arguments.

Function Application

Function application is not indicated by any markers in the syntax. In some languages a functional call can be spotted by parentheses. Although parentheses may often be required in SML in order to group elements in the manner desired by the programmer they are not part of the syntax of function application. The simple juxtaposition of two expressions signifies function application.
<expression> <expression>
If fact is a name of a function that has already been defined, then the following are examples of function application.
fact 3
fact (2+5)
fact (if 4>0 then 4 else 0)
The expression in the function position does not have to be an identifier.
(fn x => x+1) 4
(fn s => s^s) "can"

Conditional Expression

The syntax of a conditional expression
if <expression> then <expression> else <expression>
The first expression must evaluate to a boolean value. The other two expressions must have the same type. The else part is not optional.

let Expression

The syntax of the let expressions has the following form:
let <declaration> in <expression> end
The scope of the declaration is between the in and the end. Often the declaration is a value binding. For example, the expression
let val a = 5 in a+6 end
evaluates to 11, since a has the value 5 in the expression a+6. The binding of a to 5 is not visible outside the let construct. Consider the following dialog:
- let val a = 5 in a+6 end;
val it = 11 : int
- a;
Error: unbound variable or constructor: a
The identifier a is not visible at the top-level. Now, consider the following dialog:
- val b = 7;
val b = 7 : int
- let val b = 11 in b+1 end;
val it = 12 : int
- b;
val it = 7 : int
First b in bound to 7 with a top-level value binding. This binding has no influence in the expression b+1 because another binding to b hides the top-level one. In it b is bound to 11. Thus the value of the expression let val b = 11 in b+1 end is b+1 or 12. The local binding to b has no effect on the top-level binding. The value of b at the top-level remains 7.

Tuple

A tuple is a heterogeneous group of objects treated as a unit. Syntactically, a tuple is constructed using parentheses, each element is separate by a comma.
( <expression> , ... , <expression> )       (tuple)
There must be a least two elements in a tuple. Here are some examples:
(2, "string")
(1, 2, 3, 4, 5, 6)
(7-6, true, (3, "string"))
The last tuple is a triple (a 3-tuple). The third element is a pair (a 2-tuple). Note that this triple is not the same as a (7-6, true, 3, "string"). No triple can be the same as a 4-tuple.

Local Declaration

Ryan Stansifer <ryan@cs.unt.edu>
Last modified: Tue Apr 9 09:38:08 EST 1996