Lecture 016

| abstruction | type | signature | | implementation | value | structure | | mapping | function | functor |

Type class

concrete: everyone knows type (string) abstract: only implementer knows type ('a tree) parameter: only client knows type (t)

signature ORDERED =
  sig
    type t (*known to client, but not implementor*)
    val compare t*t -> order
  end

So you (client) can write code like this

structure IntLt :> ORDERED where type t = int
  = struct
      type t = int
      val compare = Int.compare
    end
structure StringLt :> ORDERED where type t = string
  = struct
      type t = string
      val compare = String.compare
    end

(it functions as if we say type t = int in signature)

// so type class is built by implementer, but implement by client

Implement Dict

Signature DICT =
  sig
    structure Key : ORDERED
    type 'a entry = Key.t * 'a
    type 'a dict
    val lookup: 'a dict -> Key.t -> 'a option
    val insert: 'a dict * 'a entry -> 'a dict
  end

===

struct StringLtDict :> DICT where type Key.t = string (*you only need to change here*)
  = struct
      structure Key = StringLt (*you only need to change here*)
      type 'a entry = Key.t * 'a
      datatype 'a dict = Empty | Node of ('a entry tree * 'a entry tree)
      val enpty = Empty
      fun lookup ... Key.compare
      fun insert ... Key.compare
    end

Important Note

IntLtDict.Key.t == IntGtDict.Key.t However: IntLtDict.dict <> IntGtDict.dict this is because keyword datatype always generate a new datatype no matter if it repeats

Functor (type class -> dict)

Functor takes in a structure (only one structure though, Syntactic Sugar make it appear that we take a structure and other things) and modify the structure to output a new structure.

For example, the code below

functor BoundedStack (structure S : STACK
                      val limit : int) :> BOUNDED_STACK =
struct
    type 'a t = 'a S.t

    exception Full

    fun push S x =
        if S.size S >= limit
          then raise Full
          else S.push S x

    fun pop S = S.pop S

    fun size S = S.size S

    val empty = S.empty
end

is the same as the code below

functor BoundedStack (UnnamedStructure :
                      sig
                        structure S : STACK
                        val limit : int
                      end) :> BOUNDED_STACK =
struct
    open UnnamedStructure
    (* same code as before *)
end

Where open is opening the name space of that module.

Don't forget about synthetic sugar. We need to give a functor a structure containing a structure even if we only intended for the functor to take in a structure.

functor TreeDict (K: ORDERED) :> DICT where type Key.t = K.t
  = struct
    structure Key = k
    ...
  end

structure LtDict = TreeDict (StringLt) (*pranphasis needed here*)
structure IntLtDict = TreeDict (IntLt)
structure IntGTDict = TreeDict (IntGt)

The other syntax

functor PairOrder (structure ox: ORDERED structure oy: ORDERED) :> ORDERED where type t = Ox.t * Oy.t
  = struct
    type t = Ox.t * Oy.t
    fun compare ((a, b), (x, y)) = (case Ox.compare (a, x) of
                                    EQUAL => Oy.compare (b, y)
                                    | order => order)
    end

structure Grid = PairOrder (structure ox = StringLt Structure oy = IntLt)
structure Board = TreeDict(Grid) (*Board.Key.t = string * int*)
val b = Board.insert (Board.empty) (("A", 1), (fn x => 2*x))
b: (int * int) Board.dict

Lab

Syntax of signature

Syntax of signature

Syntax of structure

Syntax of structure

Syntax of functor

Syntax of functor

Opaque

you can pattern match both ascription

Transparent

Transparent

Opaque

Opaque
- FIX: EMPTY SHOULD BE IN THE SIGNATURE

Pattern Match

Pattern Match

Table of Content