Lecture 12: Standard ML II

October 15

Clarification: ML has expressions (which are like the kinds of expressions e we have encountered in class) and patterns and declaration

Back to sums

We can create datatypes that are essentally sums:
- datatype boolorint = Bool of bool | Int of int;
datatype boolorint = Bool of bool | Int of int
We can treat this more like a sum and rename it boolplusint.
- datatype boolplusint = Inl of bool | Inr of int;
datatype boolplusint = Inl of bool | Inr of int
Using polymorphic datatypes, we can recreate the behavior of sums as we have used them in class:
- datatype ('a, 'b) sum = Inl of 'a | Inr of 'b;
datatype ('a,'b) sum = Inl of 'a | Inr of 'b
- Inl 1;
val it = Inl 1 : (int,'a) sum
- Inr true;
val it = Inr true : ('a,bool) sum
- fn x => case x of 
=     Inl y => y + 4
=   | Inr y => if y then 7 else 9;
val it = fn : (int,bool) sum -> int

Project: a little type checker

t ::= nat | t -> t
e ::= x | z | s(e) | e(e) | λx:t.e
We can give the code here, and then use it in SML:
- use "typecheck.sml";
[opening typecheck.sml]
datatype tp = Arrow of tp * tp | Nat
datatype tm
  = App of tm * tm
  | Lam of string * tp * tm
  | Succ of tm
  | Var of string
  | Zero
exception TypeError
val typecheck = fn : (string -> tp) -> tm -> tp
val empty = fn : string -> tp
val it = () : unit
- typecheck empty (Lam ("x", Nat, Succ(Succ(Var "x"))));
val it = Arrow (Nat,Nat) : tp
- typecheck empty (App(Zero,Zero));

uncaught exception TypeError
  raised at: typecheck.sml:30.22-30.31
We can also rewrite this typechecker with option types, which you can see here, and then use it in SML:
- use "typecheck-option.sml";
[opening typecheck-option.sml]
datatype tp = Arrow of tp * tp | Nat
datatype tm
  = App of tm * tm
  | Lam of string * tp * tm
  | Succ of tm
  | Var of string
  | Zero
val typecheck = fn : (string -> tp option) -> tm -> tp option
val empty = fn : string -> tp option
val it = () : unit
- typecheck empty (Lam ("x", Nat, Succ(Succ(Var "x"))));
val it = SOME (Arrow (Nat,Nat)) : tp option
- typecheck empty (App(Zero,Zero));
val it = NONE : tp option

Mutable state

Making references and using them:
- val r = ref 0;
val r = ref 0 : int ref
- !r;
val it = 0 : int
- val f = fn () => !r;
val f = fn : unit -> int
- f ();
val it = 0 : int
- r := 2;
val it = () : unit
- f ();
val it = 2 : int
- r := 19;
val it = () : unit
- f ();
val it = 19 : int
Making a counter:
- val count =
=   let val x = ref 0 
=   in fn () => (x := !x + 1; !x) end;
val count = fn : unit -> int
- count();
val it = 1 : int
- count();
val it = 2 : int
- count();
val it = 3 : int
- count();
val it = 4 : int
Making a counter-maker:
- val makecounter = 
=   fn () =>
=     let val x = ref 0
=     in fn () => (x := !x + 1; !x) end;
val makecounter = fn : unit -> unit -> int
- val counter1 = makecounter();
val counter1 = fn : unit -> int
- val counter2 = makecounter();
val counter2 = fn : unit -> int
- counter1 ();
val it = 1 : int
- counter1 ();
val it = 2 : int
- counter2 ();
val it = 1 : int
- counter2 ();
val it = 2 : int
- counter1 ();
val it = 3 : int

The module system

structure Foo :
  sig
    val x : int
    val y : int
    val r : bool ref
  end
- Foo.x;
val it = 12 : int
- Foo.r;
val it = ref true : bool ref
- !Foo.r;
val it = true : bool
- Foo.r := false;
val it = () : unit
- !Foo.r;
val it = false : bool

Note that what we have above is not yet much more interesting than a tuple (in fact, in the ML Crash Course session we will describe records that really look like this). However, modules will end up being much, much cooler than records.

We can use subtyping of signatures to hide information inside modules.

- signature COUNT = sig val next : unit -> int end;
signature COUNT = sig val next : unit -> int end
- 
- structure Count : COUNT = 
= struct
=   val r = ref 0
=   val next = fn () => (r := !r + 1; !r)
= end;
structure Count : COUNT
- Count.next;
val it = fn : unit -> int
- Count.r;
stdIn:121.1-121.8 Error: unbound variable or constructor: r in path Count.r

Putting types in signatures

We can put types in signatures, and we almost get an abstraction:
- signature SIG = 
= sig
=   type t
=   val x : t
=   val f : t -> t
= end;
signature SIG =
  sig
    type t
    val x : t
    val f : t -> t
  end
- 
- structure s : SIG = 
= struct
=   type t = int
=   val x = 12
=   val y = fn x => x + 1
= end;
structure s : SIG
- 
- s.x;
val it = 12 : int
Now, we can alternatively seal our signatures, which forces us to only manipulate data structures by way of the operations provided by the module.
- structure s :> SIG =
= struct
=   type t = int
=   val x = 12
=   val f = fn x => x + 1
= end;
structure s : SIG
- 
- s.x;
val it = - : s.t

$LastChangedDate: 2008-11-10 11:52:21 -0500 (Mon, 10 Nov 2008) $
$Author: rjsimmon $
$Rev: 1029 $