Clarification: ML has expressions (which are like the kinds of expressions e we have encountered in class) and patterns and declaration
- datatype boolorint = Bool of bool | Int of int; datatype boolorint = Bool of bool | Int of intWe 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 intUsing 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
t ::= nat | t -> t e ::= x | z | s(e) | e(e) | λx:t.eWe 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
- 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 : intMaking 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 : intMaking 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
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
- 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