More EPITA - Practical Programming 06 - Advanced Module Usage marwan.burelle@lse.epita.fr http://wiki-prog.infoprepa.epita.fr
Outline More 1 Embedded Declaring Module Types 2 Data Structures And Code Safety Opaque Types More Advanced Stuff 3 Playing Around Signature
More Embedded Declaring Module Types
Module Sublanguage More Embedded Declaring Module Types are not only usable at file level OCaml provides a sublanguage dedicated to modules One is able to build embedded modules, describe module interfaces or construct function over modules.
Overview More 1 Embedded Declaring Module Types Embedded Declaring Module Types
Building Local Module Embedded module Vector = struct type a t = { capacity : int; mutable size : int; tab : a array; } let make sz = { capacity = sz; size = 0; tab = Array.make sz (Obj.magic None) } let add v x = if v.size < v.capacity then begin v.tab.(v.size) <- x; v.size <- v.size + 1; end end More Embedded Declaring Module Types let v = Vector.make 10
Accessing Embedded (* module: a.ml *) module B = struct type t = int let compare a b = a - b end a.ml c.ml (* module: c.ml *) type t = A.B.t list let empty : t = [] let rec insert x = function (h::_) as l when A.B.compare x h = 0 -> l h::t when A.B.compare x h > 0 -> h :: insert x t l -> x :: l More Embedded Declaring Module Types
Overview More 1 Embedded Declaring Module Types Embedded Declaring Module Types
Embedded Module And Interface Minimal Set module type MinSetType = sig type a t (* Create an empty set *) val create : unit -> a t (* Test for emptyness *) val is_empty : a t -> bool (* Add an element *) val add : a t -> a -> unit (* Extract min value *) val take_min : a t -> a end Minimal Set module DummSet: MinSetType = struct type a t = { mutable l : a list; } let create () = {l = []} let is_empty s = s.l = [] let add s x = s.l<-x::s.l let take_min s = match List.sort compare s.l with [] -> raise Not_found h::t -> s.l <- t; h end More Embedded Declaring Module Types
Direct Interface Assignment More Typed Module module A : sig type t val compare : t -> t -> int end = struct type t = int let compare a b = Pervasives.compare a b end Embedded Declaring Module Types
Affecting Types To Existing More Forcing Types module OrderedStr : sig type t val compare : t -> t -> int end = String Embedded Declaring Module Types The existing module must provide declared symbols The new module will only provide declared symbols
More Data Structures And Code Safety Opaque Types More Advanced Stuff
Overview More 2 Data Structures And Code Safety Opaque Types More Advanced Stuff Data Structures And Code Safety Opaque Types More Advanced Stuff
Quick n Dirty Ordered Set Module set.ml type a t = a list let empty = [] let is_empty s = s = [] let rec add s = function (h::_) as l when h = s -> l h::t when h < s -> h :: add s t l -> s::l let rec mem x = function [] -> false h::t when h <= x -> x=h mem x t _ -> false let rec union s1 s2 = match (s1,s2) with ([],s) (s,[]) -> s (h1::t1, h2::_) when h1 < h2 -> h1 :: union t1 s2 (h1::_, h2::t2) when h1 > h2 -> h2 :: union s1 t2 (h1::t1, _::t2) -> h1 :: union t1 t2 More Data Structures And Code Safety Opaque Types More Advanced Stuff
Is This Module Safe? More An ordered set must verify two properties: It must be sorted It must not contain twice the same element Take a look at function add x s: if s is a correct set then: x will be added, to its right place, only if it is not in s In any case, the output set will be correct. Data Structures And Code Safety Opaque Types More Advanced Stuff If the input set is not correct, we can t guaranteed the correctness of the output. union has the same properties, and, of course, mem can only provide a correct answer if its input is a correct set.
Unsafe scenario More Unsafe use of set let myset = [1;2;4;3] let myset2 = add 3 myset Evaluation output val myset2 : int list = [1; 2; 3; 4; 3] Data Structures And Code Safety Opaque Types More Advanced Stuff The result is not a correct set, as expected.
Solutions? More Verifies properties when entering set operations: too expensive Manually check all usage of set operations: unrealistic and unsafe Enforce that only values built using Set operations are used: Probably the good solutions... Data Structures And Code Safety Opaque Types More Advanced Stuff
Overview More 2 Data Structures And Code Safety Opaque Types More Advanced Stuff Data Structures And Code Safety Opaque Types More Advanced Stuff
Hiding More If we re able to hide the fact that sets are lists, we won t be able to provide ill-formed values to set operations. That s the purpose of opaque types! set.mli type a t (* no implem *) Data Structures And Code Safety Opaque Types More Advanced Stuff val empty : a t val is_empty : a t -> bool val add : a -> a t -> a t val mem : a -> a t -> bool val union : a t -> a t -> a t
Hiding wrong.ml let main () = let l = [1;2;4;3] in let l2 = Set.add 3 l in () (* Nothing to do *) let _ = main () File "wrong.ml", line 3, characters 21-22: Error: This expression has type int list but an expression was expected of type int Set.t More Data Structures And Code Safety Opaque Types More Advanced Stuff We finaly get what we want, only value constructed by the module operations are usable!
More On Opaque Types More Enforce loose coopling: we can completely rewrite the module implementation without rewriting the code using it. With a good naming convention, data usage are more clear and self-explaining. Of course, nothing is perfect: We can t extend the module easily The module must provide all the possible operations requiring access to the inner representation. Data Structures And Code Safety Opaque Types More Advanced Stuff
Multi-Level Opacity mixed.ml module T = struct type a t = a list let empty = [] let is_empty s = s = [] let rec add s = function (h::_) as l when h = s -> l h::t when h < s -> h :: add s t l -> s::l end let set_factory n = let rec aux a = function 0 -> a n -> aux (n::a) (n-1) in aux [] n More Data Structures And Code Safety Opaque Types More Advanced Stuff mixed.mli module T : sig type a t val empty : a list val is_empty : a list -> bool val add : a -> a list -> a list end val set_factory : int -> int T.t
Overview More 2 Data Structures And Code Safety Opaque Types More Advanced Stuff Data Structures And Code Safety Opaque Types More Advanced Stuff
Phantom Types: Ref Types More Ref module Ref : sig type t val create : int -> t val set : t -> int -> unit val get : t -> int end = struct type t = int ref let create x = ref x let set t x = t := x let get t =!t end Data Structures And Code Safety Opaque Types More Advanced Stuff
Phantom Types: Building Read-Only Ref RO-Ref type readonly type readwrite module PRef : sig type a t val create : int -> readwrite t val set : readwrite t -> int -> unit val get : a t -> int val readonly : a t -> readonly t end = struct type a t = Ref.t let create = Ref.create let set = Ref.set let get = Ref.get let readonly x = x end More Data Structures And Code Safety Opaque Types More Advanced Stuff
Phantom Types More Trying let sumrefs reflist = List.fold_left (+) 0 (List.map PRef.get reflist) let increfs reflist = List.iter (fun r -> PRef.set r (PRef.get r + 1)) reflist Data Structures And Code Safety Opaque Types More Advanced Stuff Typing val sumrefs : a PRef.t list -> int val increfs : readwrite PRef.t list -> unit
More Playing Around Signature
Overview More 3 Playing Around Signature Playing Around Signature
Set With Parametric Order More Using default ordering is not always convenient, and even somtimes not possible (like objects.) The usual solution is to provide a comparison function. set.ml type a t = a list let empty = [] let is_empty s = s = [] let rec add cmp s = function (h::_) as l when cmp h s = 0 -> l h::t when cmp h s < 0 -> h :: add cmp s t l -> s::l let rec mem cmp x = function [] -> false h::t when cmp h x <= 0 -> cmp x h = 0 mem cmp x t _ -> false Playing Around Signature
Using Higher Order Parameter More The issue with the previous solution is that we need to pass the comparison function to all operations. Even if we use an opaque type, we may break the data structure by providing different comparison functions. Playing Around Signature fail! let s = Set.add (fun a b -> a - b) 1 ( Set.add (fun a b -> b - a) 2 Set.empty )
Overview More 3 Playing Around Signature Playing Around Signature
Poor Man Polymorphism More trickyset.ml module T = (* Put your module here *) type t = T.t list let empty = [] let is_empty s = s = [] let rec add s = function (h::_) as l when T.compare h s = 0 -> l h::t when T.compare h s < 0 -> h :: add s t l -> s::l let rec mem x = function [] -> false h::t when T.compare h x <= 0 -> T.compare x h = 0 mem x t _ -> false Playing Around Signature
Module As Parameter? More In the previous example, we build our module based on the existence of a module T providing what we need. The issue is that we need to copy and modify this module to build another version. What if T was a parameter to a function from module to module? Playing Around Signature This is the idea of functor: a function from modules to module
Building A Functor More We need to fix the type of the parameter. We ll define a module signature for that and then define the functor: set.ml (* Module Type of the parameter *) module type OrderedType = sig type t val compare: t -> t -> int end (* Defining The Functor *) module Make (T:OrderedType) = struct type t = T.t list (*... *) end Playing Around Signature
Functor s Code set.ml module Make (T:OrderedType) = struct type t = T.t list let empty = [] let is_empty s = s = [] let rec add s = function (h::_) as l when T.compare h s = 0 -> l h::t when T.compare h s < 0 -> h :: add s t l -> s::l let rec mem x = function [] -> false h::t when T.compare h x <= 0 -> T.compare x h = 0 mem x t _ -> false end More Playing Around Signature
Using Our Functor More Set of int module Int = struct type t = int let compare a b = Pervasives.compare a b end Playing Around Signature module IntSet = Set.Make(Int)
Functor Instantiation Is A Module More Type of our functor module IntSet : sig type t = Int.t list val empty : a list val is_empty : a list -> bool val add : Int.t -> Int.t list -> Int.t list val mem : Int.t -> Int.t list -> bool end Playing Around Signature
Overview More 3 Playing Around Signature Playing Around Signature
Functor s Signature More Using.mli file set.mli module type OrderedType = sig type t val compare : t -> t -> int end Playing Around Signature module Make : functor (T : OrderedType) -> sig type t = T.t list val empty : t val is_empty : t -> bool val add : T.t -> t -> t val mem : T.t -> t -> bool end
And Opaque Types More You can rewrite the signature with an opaque version for the type t This form will prevent usage of ill-formed set. It also protect us from mixing containers built on the same type but with different comparison functions. Playing Around Signature
Set Of String strset.ml module Str = struct type t = string let compare a b = compare (String.lowercase a) (String.lowercase b) end module NoCaseStrSet = Set.Make(Str) module CaseStrSet = Set.Make(String) More Playing Around Signature let s = NoCaseStrSet.add "Aa" ( CaseStrSet.add "AA" ( CaseStrSet.add "aa" CaseStrSet.empty ) )
Functor s Signature More Using module signature set.ml module type S = sig type content type t val empty : t val is_empty : t -> bool val add : content -> t -> t val mem : content -> t -> bool end module Make (T:OrderedType) = struct type content = T.t type t = content list (*... *) end Playing Around Signature
Functor s Signature More Using module signature set.mli module type S = sig type content type t val empty : t val is_empty : t -> bool val add : content -> t -> t val mem : content -> t -> bool end module Make (T : OrderedType) : S Playing Around Signature
Functor s Signature More The previous solution as a major drawback... Too bad module StrSet = Set.Make(String) Playing Around Signature let s = StrSet.add "aa" StrSet.empty Error: This expression has type string but an expression was expected of type StrSet.content = Set.Make(String).content
Functor s Signature More Using module signature (this one is working... ) set.mli module type S = sig type content type t val empty : t val is_empty : t -> bool val add : content -> t -> t val mem : content -> t -> bool end module Make (T : OrderedType) : S with type content = T.t Playing Around Signature