16.1 Programs in the interactive mode
An interactive mode program of SML# is a sequence of declarations, terminated by semicolon (;). The following shows a simple interactive program session of SML#
$ smlsharp
SML# 4.1.0 ...
# fun fact 0 = 1
> | fact n = n * fact (n - 1);
> val x = fact 10;
val fact = fn : int -> int
val x = 3628800 : int
# and > are the first-line prompt character and the second-line prompt character, respectively. As shown in this example, the SML# interactive compiler print the result of evaluation of the input program.
Declarations are divided into the core language declarations decl, which generates values such as functions and records, and the module language declarations strDecl, which are named collections of core language declarations.
interactiveProgram | ::= | ; |
decl interactiveProgram | ||
topdecl interactiveProgram |
-
•
Core language declarations
The following shows core language declarations (decl) and simple examples.
decl ::= infixDecl infix declaration valDecl val delaration valRecDecl val rec delaration funDecl function delaration datatypeDecl data type delaration typeDecl type alias delaration exceptionDecl exception delaration localDecl local declaration Declaration kind Simple example infix declaration infix 4 = val declaration val x = 1 val rec declaration val rec f = fn x => if x = 0 then 1 else x * f (x - 1) function declaration fun f x = if x = 0 then 1 else x * f (x - 1) datatye declaration datatype foo = A | B type declaration type person = {name:string, age:int} exception declaration exception Fail of string local declaration local val x = 2 in val y = x + x end -
•
Module language declarations
The following shows module language declarations (strDecl) and simple examples.
topdecl ::= strDecl structure delaration sigDecl signature delaration functorDecl functor delaration localTopdecl local declaration structure declaration structure Version =
struct
val version = "2.0.0"
endsignature declaration signature VERSION =
sig
val version :string
endfunctor declaration functor System(V:VERSION) =
struct
val name = "SML#"
val version = V.version
endlocal declaration local
structure V = Version
in
structure Release = struct
val version = V.version
val date = "2021-11-08"
end
end
16.1.1 Evaluation of core language declarations
Execution of an interactive program is done by evaluating a list of declaration sequentially. Evaluation of a core language declaration has the effect of evaluating the components, such as expressions, contained in the declaration, and binding the identifiers defined in the declaration to the values generated by the evaluation of the components. Generated values are either static or dynamic.
Static values are compile-time value generated by the compiler. They are one of the following.
- infix operator status
-
This indicates that an identifier is parsed as an infix operator. It also has the associatibity (right associative or left associative) and association strength (from 0 to 9).
- constructor status
-
This indicates that an identifier status is a data constructor. An identifier with the constructor status only matches with the corresponding constructor in pattern marching.
- type constructor
-
Type constructors are newly defined types generated by datatype declarations. Parametric types with type parameters can be defined.
- type
-
Types specify a static property of a dynamic values produced by executing the corresponding program fragment.
The SML# compiler statically evaluates declarations and generates static values, and bind names defined in the declarations to those static values. For declarations such as valDecl that implies program execution, the compiler generates executable code, which when executed, constructs dynamic values at runtime and binds names to those dynamic values.
Dynamic values are one of the following.
- Built-in values
-
Runtime data of built-in types defined in Chapter 18. They include atomic values, lists, arrays, and vectors. For example, values of type int32 are 32-bit signed integers whose representation is defined by the underlying machine architecture.
- Function closures
-
They are runtime representations of values of function types.
- Data constructors
-
They are built-in functions to construct datatype representations.
- Exception closures
-
They are built-in functions to construct exception values.
- Records
-
They are values of record and tuple types.
- Datatype representation
-
They are generated by data constructors defined in datatype declarations.
- Exception values
-
They are generated by exception constructors.
The following show a summary of the generated static and dynamic values for each of declarations.
declaration class static values bindings dynamic values bindings assigned to infix declaration infix status variables, constructor names val delaration types dynamic values corresponding to types variables val rec delaration types function closures variables function delaration types function closures variables data type delaration type constructors type constructor names constructor status data constructors data constructor names type alias delaration type function type constructor names exception delaration constructor status exception constructors exception constructor names exception delaration depending on the contents depending on the contents depending on the contents
We outline declaration evaluation and the resulting value bindings below. The detailed syntax and semantics of each of these declaration classes are given in Chapter 23.
- infixDecl
-
This is a declaration of the form infix . The identifier is given infix operator property. is optional. The following shows a simple example.
# infix 7 *;
# infix 8 ^;
# fun x ^ y = if y = 0 then 1 else x * x ^ (y - 1);
val ^ = fn : int * int -> int
# val a = 4 * 3 ^ 2
val a = 36 : intSince * and ^ has infix precedence and , respectively, 4 * 3 ^ 2 is elaborated to *(4, ^ (3,2)).
- valDecl
-
This is a declaration of the form val = . Evaluation is done by evaluating and checking whether the resulting dynamic value matches . If it matches then variables in are bound to the corresponding values of the result. The simplest example is the following variable binding.
# val x = 1;
val x = 1 : intIn this example, variable x is bound to the result of evaluating expression 1, namely static type int and dynamic value .
# val (x, y) = (1, 2);
val x = 1 : int
val y = 2 : int - valRecDecl
-
Val declarations restricted to (mutually recursive) functions.
# val rec even = fn x => if x = 0 then true else odd (x - 1)
> and odd = fn x => if x = 1 then true else odd (x - 1);
val even = fn : int -> bool
val odd = fn : int -> boolIn this declaration, each identifier is bound to the corresponding type and function value.
- funDecl
-
This is for mutually recursive function definitions.
# fun even x = if x = 0 then true else odd (x - 1)
> and odd x = if x = 1 then true else odd (x - 1);
val even = fn : int -> bool
val odd = fn : int -> boolIn this declaration, each identifier is bound to the corresponding type and function value.
- datatypeDecl
-
This defines new mutually recursive type constructors.
# datatype foo = A of int | B of bar and bar = C of bool | D of foo;
datatype bar = C of bool | D of foo
datatype foo = A of int | B of bar
# D (A 3);
val it = D (A 3) : barEvaluation of this declaration generates two new type constructors (with no type parameter) foo and bar and the identifiers foo and bar are bound to them. It also generate data constructors A, B and C, D for foo and bar, respectively, and the identifiers to them. These identifiers are given the constructor status.
As in the above explanation, in this manual, we generally identify type constructors such as foo and data constructors such as A with their names foo and A.
- typeDecl
-
This bind an identifier to a type or a type function.
# type ’a foo = ’a * ’a;
type ’a foo = ’a * ’a
# fun f (x:int foo) = x;
val f = fn : int * int -> int * intEvaluation of this generates a type function that takes a type parameter represented by ’a and returns a type ’a * ’a, and binds identifier foo to this type function. In the scope of this declaration, foo is used as an alias of * .
- exceptionDecl
-
This defines exception constructors.
# exception Foo of int;
exception Foo of intEvaluation of this declaration generates a new exception constructor with a parameter of type int. In the scope of this declaration, the identifier foo is given the constructor status and is bound to the exception constructor.
- localDecl
-
Declarations between local and in are local until end.
# local
> val x = 2
> in
> val y = x + x
> end;
val y = 4 : intThe scope of the declaration val x = 2 is until end. The variable x is not visible from outside of this local declaration and therefore only y is printed in the interactive session.
16.1.2 Evaluation of module language declarations
A structure declaration, the main component of the module language, is a mechanism to bundle a list of declarations and gives it a name. Evaluation of a structure expression yields a static type environment representing the static binding of the declarations, and a dynamic value environment representing the dynamic binding of the declarations. The effect of evaluation of a structure declaration is to extend the current type environment and the current value environment with the static and dynamic environment obtained from the generated environments by prefixing the bound names in the environments with the structure name.
For example,the structure expression
struct
val version = "4.1.0"
end
generates the type environment and the runtime environment . Therefore the structure declaration
structure Version =
struct
val version = "4.1.0"
end
has the effect of extending the current type environment and the current runtime-environment with the following binding of long names: , and .
A signature constraint can be specified to a structure expression. A signature statically constrains the type environment generated by the structure. In addition to type constraints to variables, constraints of type declarations can be specified. A structure expression with a signature constraint generates a type environment that only contains those names that are specified in the signature.
A functor is a function that takes a structure and returns a structure. Different from functions in the core language functor definitions are restricted to first-order and top-level.
We show evaluation of module language declarations in the interactive mode below. The detailed syntax and semantics of each of these declaration classes are given in Chapter 24.
- strDecl
-
This declaration defines a structure.
# structure Version =
> struct
> val version = "4.1.0"
> end;
structure Version =
struct
val version = "4.1.0" : string
endEvaluation of this declaration binds Version to a type environment representing the structure containing version component, and long identifier Version.version to string type and the dynamic value "4.1.0".
- sigDecl
-
This declaration binds an identifier to a signature.
# signature VERSION =
> sig
> val version : string
> end;
signature VERSION =
struct
val version : string
endEvaluation of this declaration binds VERSION to the specified signature. Signatures describes types of components of SML# structures.
- functorDecl
-
This declaration defines a functor, which is a function taking a structure and returning a structure.
functor System(V:VERSION) =
struct
val name = "SML#"
val version = V.version
end;Evaluation of this declaration binds the identifier System to a functor that takes a structure of VERSION signature and returns a structure containing name and version components.
- localTopdecl
-
Declarations between local and in are local until end.
# local
> structure V = Version
> in
> structure Release = struct
> val date = "2021-11-08"
> val version = V.version
> end
> end;
val Release =
struct
val date = "2021-11-08" : string
val version = "4.1.0" : string
endThe scope of the declaration structure V = Version is until end. The structure V is not visible from outside of this local declaration and therefore only Release is printed in the interactive session.