| abstruction | type | signature | | implementation | value | structure | | mapping | function | functor |
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
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
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 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
functor only takes in one structure,
otherwise, write structures within a structure
you can pattern match both ascription - FIX: EMPTY SHOULD BE IN THE SIGNATURE
Table of Content