\( \newcommand\D{\mathrm{d}} \newcommand\E{\mathrm{e}} \newcommand\I{\mathrm{i}} \newcommand\bigOh{\mathcal{O}} \newcommand{\cat}[1]{\mathbf{#1}} \newcommand\curl{\vec{\nabla}\times} \newcommand{\CC}{\mathbb{C}} \newcommand{\NN}{\mathbb{N}} \newcommand{\QQ}{\mathbb{Q}} \newcommand{\RR}{\mathbb{R}} \newcommand{\ZZ}{\mathbb{Z}} \)
UP | HOME

Type Theory of SML

Table of Contents

1. Overview

Standard ML appears to be prenex polymorphic typed-lambda calculus, but it is a Hindley-Milner system. But I'm skeptical whether this is a good intuition, since it seems algebraic data types match initial algebras for endofunctors.

I'm going to have to examine the definition in greater detail, but I do not know what the type theoretic structure of a datatype (algebraic data type) is, exactly. I suspect it is a iso-recursive variant type, if I may borrow the terminology of TAPL.

2. Values and Value Restriction

If we allow anything to appear in a reference type, then we can quickly run into problems when storing a function in a reference. Because of this, Standard ML restricts what values may be stored in a reference type. The 1997 definition calls them Non-Expansive Expressions and defines the in chapter 4 section 7.

These are precisely "values", in the sense of TAPL.

After desugaring an expression and performing the expansions defined in the parsing step, the grammar for them in quasi-EBNF:

nexp = scon              ; special constants = literal values
     | ["op"] longvid    ; identifiers
     | "{" [nexprow] "}" ; record types
     | "(" nexp ")"
     | conexp nexp       ; constructors applied to nexp
     | nexp ":" ty
     | "fn" match        ; anonymous functions

nexprow = lab "=" nexp ["," nexprow]

conexp = "(" conexp [":" ty] ")"
       | ["op"] longvid

The only constraints here are that within a conexp we require longvid is not ref, and is of \(C(longvid)\in\{\mathtt{c},\mathtt{e}\}\).

We should actually think of "non-expansive expressions" as "syntactic values". (If we compare them to semantic values found in chapter 6, then we're actually pretty close.)

Observe that ref NONE is not a non-expansive expression because we could do terrible things, like:

val r: 'a option ref = ref NONE
val r1: string option ref = r
val r2: int option ref = r
val () = r1 := SOME "foo"
val v: int = valOf (!r2)

Also observe:

  • Function applications are expansive expressions
  • Let-expressions are expansive expressions
  • Raising or handling exceptions are expansive expressions
  • Conditionals and case-expressions are expansive expressions

Really, any expressions which can be evaluated further can create new reference cells or raise exceptions, which are expansive (in the sense that they expand memory usage).

3. Polymorphism

The notion of the "closure" operation when elaborating an expression (the Clos operation defined in section 4.8) occurs in rule (15) of the definition of Standard ML and is responsible for transforming an expression into a polymorphic expression. This "Clos" operation enforces value restriction by preventing a declaration binding an identifier to an expansive expression from becoming polymorphic.

The comment to rule (15) is also worth pondering.

4. References

Last Updated 2023-02-27 Mon 07:43.