SML# Document Version 4.0.0
16 The SML# Structure

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.0.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"
      end
    signature declaration signature VERSION =
      sig
        val version :string
      end
    functor declaration functor System(V:VERSION) =
      struct
        val name = "SML#"
        val version = V.version
      end
    local declaration local
      structure V = Version
    in
      structure Release = struct
        val version = V.version
        val date = "2021-04-06"
      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 id. The identifier id is given infix operator property. n 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 : int

Since * and ^ has infix precedence 7 and 8, respectively, 4 * 3 ^ 2 is elaborated to *(4, ^ (3,2)).

valDecl

This is a declaration of the form val pat = exp. Evaluation is done by evaluating exp and checking whether the resulting dynamic value matches pat. If it matches then variables in pat are bound to the corresponding values of the result. The simplest example is the following variable binding.

# val x = 1;
val x = 1 : int

In this example, variable x is bound to the result of evaluating expression 1, namely static type int and dynamic value 1.

# val (x, y) = (1, 2);
val x = 1 : int
val y = 2 : int

The details of expressions exp and patterns are given in Chapter 19 and 20. .

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 -> bool

In 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 -> bool

In 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) : bar

Evaluation 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 * int

Evaluation 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 int

Evaluation 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 : int

The 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.0.0"
end

generates the type environment {𝚟𝚎𝚛𝚜𝚒𝚘𝚗:𝚜𝚝𝚛𝚒𝚗𝚐} and the runtime environment {𝚟𝚎𝚛𝚜𝚒𝚘𝚗:"3.4.0"}. Therefore the structure declaration

structure Version =
  struct
    val version = "4.0.0"
  end

has the effect of extending the current type environment and the current runtime-environment with the following binding of long names: {Version.version:𝚜𝚝𝚛𝚒𝚗𝚐}, and {Version.version:"3.4.0"}.

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.0.0"
>   end;
structure Version =
struct
  val version = "4.0.0" : string
end

Evaluation 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.0.0".

sigDecl

This declaration binds an identifier to a signature.

# signature VERSION =
>  sig
>    val version : string
>  end;
signature VERSION =
struct
  val version : string
end

Evaluation 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-04-06"
>     val version = V.version
>   end
> end;
val Release =
struct
  val date = "2021-04-06" : string
  val version = "4.0.0" : string
end

The 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.