Constructs that have been left out of this simple subset are records, patterns, exceptions, signatures, and structures.
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> ::= use "<file name>" ;A semicolon is needed by the interactive SML system to terminate the user's input.
A declaration binds a name or names to values. In response the SML system prints the name, value, and type of each object bound. For example,
- val n = 2*3; val n = 6 : int
When an expression is typed at the top-level the SML system evalauates it and prints the value and its type. For convenience the SML system implicitly binds it to the identifer it. In effect, an expressions is treated as though it were the declaration
val it = <expression> ;So, the SML reponse is the same as for declarations. For example,
- 2*3; val it = 6 : intThe user can use the identifier it as the name of the last expression the user typed in. Without a name the object cannot be refered to by the programmer again.
The most common directive is the use directive (although it is really a function with a side effect). The use directive tells the SML system to take input from the file named by the argument.
<expression> ::= <identifier> (object name) <expression> ::= <identifier> (constructor name) <expression> ::= <literal> <expression> ::= <expression> <expression> (function application) <expression> ::= fn <identifier> => <expression> (anonymous function) <expression> ::= if <expression> then <expression> else <expression> (conditional expression) <expression> ::= ( <expression> ... <expression> ) (tuple) <expression> ::= let <declaration> in <expression> end (let expression) <expression> ::= ( <expression> ) (parenthesized)
<declaration> ::= val <identifier> = <expression> (value 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)
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 | downintroduces 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> ::= <constructor name> <constructor binding> ::= <constructor name> of <type>
<type> ::= int <type> ::= bool <type> ::= real <type> ::= string <type> ::= unit <type> ::= <type> list <type> ::= <type> -> <type> <type> ::= <type> * ... * <type>
let val a = 1+2; in a+a (* "a" denotes the value 3 here *) endThe 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 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 : colorObserve, 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> ::= <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.
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+1The 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^sThis 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.
<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"
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 <declaration> in <expression> endThe 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 endevaluates 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: aThe 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 : intFirst 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.
( <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.