Existential Types and Module Systems Xiaoheng Ji Department of Computing and Software March 19, 2004.

Slides:



Advertisements
Similar presentations
Sml2java a source to source translator Justin Koser, Haakon Larsen, Jeffrey Vaughan PLI 2003 DP-COOL.
Advertisements

More ML Compiling Techniques David Walker. Today More data structures lists More functions More modules.
Programming Languages
Data Structure & Abstract Data Type
Getting started with ML ML is a functional programming language. ML is statically typed: The types of literals, values, expressions and functions in a.
ML: a quasi-functional language with strong typing Conventional syntax: - val x = 5; (*user input *) val x = 5: int (*system response*) - fun len lis =
C++ How to Program, 7/e © by Pearson Education, Inc. All Rights Reserved.
Abstract Data Types Data abstraction, or abstract data types, is a programming methodology where one defines not only the data structure to be used, but.
Classes and Object- Oriented... tMyn1 Classes and Object-Oriented Programming The essence of object-oriented programming is that you write programs in.
CPS 506 Comparative Programming Languages Abstract Data Type and Encapsulation.
ISBN Chapter 11 Abstract Data Types and Encapsulation Concepts.
Chapter 3 Data Abstraction: The Walls. © 2005 Pearson Addison-Wesley. All rights reserved3-2 Abstract Data Types Modularity –Keeps the complexity of a.
OOP in Java Nelson Padua-Perez Chau-Wen Tseng Department of Computer Science University of Maryland, College Park.
ML: a quasi-functional language with strong typing Conventional syntax: - val x = 5; (*user input *) val x = 5: int (*system response*) - fun len lis =
Catriel Beeri Pls/Winter 2004/5 type reconstruction 1 Type Reconstruction & Parametric Polymorphism  Introduction  Unification and type reconstruction.
© 2006 Pearson Addison-Wesley. All rights reserved4-1 Chapter 4 Data Abstraction: The Walls.
Introduction to ML Last time: Basics: integers, Booleans, tuples,... simple functions introduction to data types This time, we continue writing an evaluator.
Run time vs. Compile time
Modules in UHC A proposal by: Tom Hofte & Eric Eijkelenboom.
Functional programming: LISP Originally developed for symbolic computing First interactive, interpreted language Dynamic typing: values have types, variables.
Data Abstraction and Object- Oriented Programming CS351 – Programming Paradigms.
Abstract data types & object-oriented paradigm. Abstraction Abstraction: a view of an entity that includes only the attributes of significance in a particular.
Lecture 9 Concepts of Programming Languages
Abstract Data Types and Encapsulation Concepts
 2006 Pearson Education, Inc. All rights reserved Generics.
© Kenneth C. Louden, Chapter 9 – Abstract Data Types and Modules Programming Languages: Principles and Practice, 2nd Ed. Kenneth C. Louden.
C++ How to Program, 8/e © by Pearson Education, Inc. All Rights Reserved. Note: C How to Program, Chapter 22 is a copy of C++ How to Program Chapter.
Chapter 3 Introduction to Collections – Stacks Modified
PrasadCS7761 Haskell Data Types/ADT/Modules Type/Class Hierarchy Lazy Functional Language.
Computer Science and Engineering College of Engineering The Ohio State University Interfaces The credit for these slides goes to Professor Paul Sivilotti.
Chapter 06 (Part I) Functions and an Introduction to Recursion.
Patterns in OCaml functions. Formal vs. actual parameters Here's a function definition (in C): –int add (int x, int y) { return x + y; } –x and y are.
Defining New Types Lecture 21 Hartmut Kaiser
Functional Programming With examples in F#. Pure Functional Programming Functional programming involves evaluating expressions rather than executing commands.
Data Structures Using C++ 2E1 Inheritance An “is-a” relationship –Example: “every employee is a person” Allows new class creation from existing classes.
A Third Look At ML Chapter NineModern Programming Languages, 2nd ed.1.
ISBN Chapter 11 Abstract Data Types and Encapsulation Concepts.
Simple Classes. ADTs A specification for a real world data item –defines types and valid ranges –defines valid operations on the data. Specification is.
© M. Winter COSC 4P41 – Functional Programming Modules in Haskell Using modules to structure a large program has a number of advantages: Parts of.
Chapter 10, Slide 1 ABSTRACT DATA TYPES Based on the fundamental concept of ABSTRACTION:  process abstraction  data abstraction Both provide:  information.
ISBN Chapter 11 Abstract Data Types and Encapsulation Concepts.
Object-Oriented Programming Chapter Chapter
ISBN Object-Oriented Programming Chapter Chapter
Application development with Java Lecture 21. Inheritance Subclasses Overriding Object class.
CSE 3302 Programming Languages Chengkai Li, Weimin He Spring 2008 Abstract Data Types and Modules Lecture 11 – ADT and Modules, Spring CSE3302 Programming.
Programming Fundamentals. Topics to be covered Today Recursion Inline Functions Scope and Storage Class A simple class Constructor Destructor.
1 Chapter 11 © 1998 by Addison Wesley Longman, Inc The Concept of Abstraction - The concept of abstraction is fundamental in programming - Nearly.
1 CS Programming Languages Class 22 November 14, 2000.
(1) ICS 313: Programming Language Theory Chapter 11: Abstract Data Types (Data Abstraction)
1 Copyright © 1998 by Addison Wesley Longman, Inc. Chapter 10 Abstraction - The concept of abstraction is fundamental in programming - Nearly all programming.
Advanced Functional Programming Tim Sheard 1 Lecture 17 Advanced Functional Programming Tim Sheard Oregon Graduate Institute of Science & Technology Lecture:
ISBN Chapter 11 Abstract Data Types and Encapsulation Concepts.
CMSC 330: Organization of Programming Languages Operational Semantics.
Defining Classes Modules and ADTs CSCE 314 Spring 2016.
(C) 2010 Pearson Education, Inc. All rights reserved.  Best way to develop and maintain a large program is to construct it from small, simple pieces,
Abstract Data Types and Encapsulation Concepts
Chapter 3: Using Methods, Classes, and Objects
11.1 The Concept of Abstraction
Lecture 9 Concepts of Programming Languages
Abstract Data Types and Encapsulation Concepts
Abstract Data Types and Encapsulation Concepts
Chapter 6 Methods: A Deeper Look
FP Foundations, Scheme In Text: Chapter 14.
CSE-321 Programming Languages Introduction to Functional Programming
Classes, Objects and Methods
CSCE 314: Programming Languages Dr. Dylan Shell
11.1 The Concept of Abstraction
Lecture 9 Concepts of Programming Languages
Chapter 11 Abstraction - The concept of abstraction is fundamental in
Presentation transcript:

Existential Types and Module Systems Xiaoheng Ji Department of Computing and Software March 19, 2004

Data Abstraction The fundamental idea: the separation of the client from the implementor of the abstraction by an interface. The central ideas: –Define a representation type together with operations that manipulate values of that type. –Hold the type representation type abstract from client of the ADT to ensure representation independence. ``Abstract types have existential type'' [Mitchell and Plotkin 84] Existential types provide the fundamental linguistic mechanisms for defining interfaces, implementing them, and using the implementation in client code. Existential types are a kind of type originally developed in constructive logic.

Existential Types Two different ways of looking at an existential type, written {  X, T} or  X.T –logical intuition: an element of {  X, T} is a value of type [X=>S]T, for some type S. –Operational intuition: an element of {  X, T} is a pair, written {*S, t}, of a type S and a term t of type [X  S]T. Instead of  X.T, the nonstandard notation {  X, T} suggests that the existential value is a mixed type-value tuple.

Existential Types - Overview Typing and evaluation rules Introducing ADTs –Introducing ADTs –Introducing objects –Objects vs. ADTs Encoding existential types

Existential introduction an existentially typed value introduced by pairing a type with a term: {*S,t} intuition: value {*S,t} of type {  X,T} is a module with a type component S and a term component t, where [S/X]T. The type S is often called the hidden representation type, or sometimes the witness type of the package.

Example p = {*Nat, {a = 0, f = x : Int.succ(x)}} as { ∃ X.{a : X, f : X->X}}; p : { ∃ X.{a : X, f : X->X}} q = {*Nat, {a = 0, f = x : Int.succ(x)}} as { ∃ X.{a : X, f : X->Nat}}; q : { ∃ X.{a : X, f : X->X}} Nat – type component {a = 0, f = x : Int.succ(x)} – term component as { ∃ X.{a : X, f : X  Nat}} – type annotation

Typing rule for existential introduction A value of type  X.T is a package with a witness type T‘ for X and a value term t : [X -> T']T. –pack X = T' with t as T :  X.T (conventional notation) –{*T', t} as {  X, T} (Pierce's notation) The Introduction typing rule for existential types:  |- t 1 : [X => U]T 1  |- {*U, t 1 } as {  X, T 1 } : {  X, T 1 } (T- PACK)

Examples of Existential types {*Nat, 0} as {  X, X} : {  X, X} {*Bool, true} as {  X, X} : {  X, X} p = {*Nat, {a = 0, f = x : Nat. succ x}} as {  X, {a : X, f : X -> Nat}} p : {  X, {a : X, f : X -> Nat}} q = {*Bool, {a = true, f = x : Bool. 0}} as {  X, {a : X, f : X -> Nat}} q : {  X, {a : X, f : X -> Nat}} The type part is hidden (opaque, abstract), and the value part provides an interface for interpreting the hidden type.

Unpacking existential values Unpacking an existential: let {X,x} = t1 in t2 Type variable X cannot occur in T2 -- it is not in scope (i.e. doesn't appear in the context  ). This means that the name X of the existential witness type cannot "escape“ from the let expression. Also, within the body t2, the type X is abstract and can only be used through the interface provided by x : T1.  |- t 1 : {  X, T 1 }  |- let {X,x} = t 1 in t 2 : T 2 (T- UNPACK) , X, x: T 1 |- t 2 : T 2

Example p = {*Nat, {a = 0, f = x : Nat. succ x}} as {  X, {a : X, f : X -> Nat}} p : {  X, {a : X, f : X -> Nat}} The elimination expression let { X, x} = p in ( x.f x.a) ; ⊳ 1 : Nat opens p and uses the fields of its body (x.f and x.a) to compute a numeric result.

Example p = {*Nat, {a = 0, f = x : Nat. succ x}} as {  X, {a : X, f : X -> Nat}} p : {  X, {a : X, f : X -> Nat}} The body of the elimination form can also involve the type variable x: let { X, x} = p in ( y:X. (x.f y) ) x.a; ⊳ 1 : Nat The fact that the package’s representation type is held abstract during the typechecking of the body means that the only operations allowed on x are those warranted by its ``abstract type'‘ {a : X, f : X->Nat}.

Example p = {*Nat, {a = 0, f = x : Nat. succ x}} as {  X, {a : X, f : X -> Nat}} p : {  X, {a : X, f : X -> Nat}} all operations on term x must be warranted by its abstract type, e.g. we cannot use x.a concretely as a number (since the concrete type of the module is hidden): let { X, x} = p in succ(x.a); ⊳ Error: argument of succ is not a number

Example p = {*Nat, {a = 0, f = x : Nat. succ x}} as {  X, {a : X, f : X -> Nat}} p : {  X, {a : X, f : X -> Nat}} In the rule T-UNPACK, the type variable X appears in the context in which t2’s type is calculated, but does not appear in the context of the rule’s conclusion. This means that the result type T2 cannot contain X free, since any free occurrences of X will be out of scope in the conclusion. let { X, x} = p in x.a; ⊳ Error: Scoping error! must add side condition to typing rule for existential elimination; X may not occur in the result type

Evaluation rule for existentials let {X, x} = ({*T 11, v 12 } as T 1 ) in t 2  [X->T 11 ] [x->v 12 ] t 2 (E- UNPACKPACK) If the first subexpression of the let has already been reduced to a concrete package, then we may substitute the components of this package for the variables X and x in the body t 2. In terms of analogy with modules, this rule can be viewed as a linking step, in which symbolic names (X and x) referring to the components of a separately compiled module are replaced by the actual contents of the module. Since the type variable X is substituted away by this rule, the resulting program actually has concrete access to the package’s internals.

Existential Types - Overview Typing and evaluation rules Introducing ADTs –Introducing ADTs –Introducing objects –Objects vs. ADTs Encoding existential types

Parametricity consider: p = {*Nat, {a = 0, f = x : Nat. 0}} as {  X, {a : X, f : X -> Nat}} q = {*Bool, {a = false, f = x : Bool. 0}} as {  X, {a : X, f : X -> Nat}} evaluation does not depend on the specific type of p and q: it is parametric in X: let { X, x} = p in (x.f x.a); ⊳ 0 let { X, x} = q in (x.f x.a); ⊳ 0 Idea: use parametricity to construct two kinds of programmer defined abstractions: abstract data types (ADTs) and objects

Existential Types - Overview Typing and evaluation rules Introducing ADTs –Introducing ADTs –Introducing objects –Objects vs. ADTs Encoding existential types

Abstract Data Types A conventional abstract data type (or ADT) consists of: A type name A A concrete representation type T Implementations of some operations for creating, querying, and manipulating values of type T An abstraction boundary enclosing the representation and operations Inside this boundary, elements of the type are viewed concretely (with type T). Outside, they are viewed abstractly, with type A. Values of type A may be passed around, stored in data structures, etc., but not directly examined or changed – the only operations allowed on A are those provided by the ADT.

Example signature COUNTER = sig type counter val new : counter val get : counter -> Nat val inc : counter -> counter end; abstract representation type concrete representation type interface implementation structure Counter :> COUNTER = struct type counter = Nat val new = 1 fun get(n) = n fun inc(n) = n + 1 end;

Example signature COUNTER = sig type counter val new : counter val get : counter -> Nat val inc : counter -> counter end; structure Counter :> COUNTER = struct type counter = Nat val new = 1 fun get(n) = n fun inc(n) = n + 1 end; - counter.get ( counter.inc counter.new); val it = 2 : Nat

ADTs as existentials signature COUNTER = sig type counter val new : counter val get : counter -> Nat val inc : counter -> counter end; structure Counter :> COUNTER = struct type counter = Nat val new = 1 fun get(n) = n fun inc(n) = n + 1 end; COUNTER =  Counter.{ new : Counter, get : Counter -> Nat, inc : Counter -> Counter} counterADT = {*Nat, { new = 1, get(n) = n : Nat. n inc(n) = n : Nat. succ(n) }} As COUNTER ``Abstract types have existential type'‘ [Mitchell and Plotkin 84]

ADTs as existentials COUNTER =  Counter.{ new : Counter, get : Counter -> Nat, inc : Counter -> Counter CounterADT = {*Nat, { new = 1, get(n) = n : Nat. n inc(n) = n : Nat. succ(n) }} As COUNTER type name Counter can be used just like a new base type e.g. we can define new ADTs with representation type Counter, e.g. a FlipFlop let {Counter, counter} = CounterADT in counter.get ( counter.inc counter.new); ⊳ 2 : Nat

Flip-Flop Let {Counter, counter} = counterADT in Let {FlipFlop, flipflop} = {*Counter, {new = counter.new, read = c : Counter. iseven (counter.get c), toggle = c : Counter. counter.inc c, reset = c : Counter. counter.new}} As {  FlipFlop, {new: FlipFlop, read: FlipFlop->Bool, toggle: FlipFlop->FlipFlop, reset: FlipFlop->FlipFlop}} in flipflop.read (flipflop.toggle (flipflop.toggle flipflop.new)); ⊳ false : Bool

Representation independence Alternative implementation of the CounterADT: counterADT = {*{x:Nat}, {new = {x=1}, get = i : {x:Nat}. i.x, inc = i : {x:Nat}. {x=succ(i, x)}}} as {  Counter, {new: Counter, get: Counter->Nat, inc: Counter->Counter}}; ⊳ counterADT : {  Counter, {new: Counter, get: Counter->Nat, inc: Counter->Counter}} Representation independence: follows from parametricity: the whole program remains typesafe since the counter instances cannot be accessed except using ADT operations

ADT-style of programming yields huge improvements in robustness and maintainability of large systems: –limits the scope of changes to the program –encourages the programmer to limit the dependencies between parts of the program (by making the signatures of the ADTs as small as possible) –forces programmers to think about designing abstractions

Existential Types - Overview Typing and evaluation rules Introducing ADTs –Introducing ADTs –Introducing objects –Objects vs. ADTs Encoding existential types

Existential objects two basic components: internal state, methods to manipulate the state. e.g., a counter object holding the value 5 might be written c = {*Nat, {state = 5, methods = {get = x : Nat. x, inc = x : Nat. succ(x)}}} as Counter; where: Counter = {  X, {state:X, methods: {get: X->Nat, inc: X->X}}};

Invoking the get method c = {*Nat, {state = 5, methods = {get = x : Nat. x, inc = x : Nat. succ(x)}}} as  X, { state: X, methods: { get: X->Nat, inc: X->X}} let {X, body} = c in body.methods.get (body.state); ⊳ 5: Nat

Encapsulating the get method C =  X, { state: X, methods: { get: X->Nat, inc: X->X}} sendget = c: Counter. let {X, body} = c in body.methods.get (body.state); ⊳ sendget : Counter -> Nat

Invoking the inc method c = {*Nat, {state = 5, methods = {get = x : Nat. x, inc = x : Nat. succ(x)}}} as  X. { state: X, methods: { get: X->Nat, inc: X->X}} let {X, body} = c in body.methods.inc (body.state); ⊳ Error: scoping error Why? X appears free in the body of the let

Encapsulating the inc method in order to properly invoke the inc method, we must repackage the fresh internal state as a counter object: c1 = let {X, body} = c in {*X, {state = body.methods.inc (body.state), methods = body.methods}} as Counter; sendinc = c : Counter. let {X, body} = c in {*X, {state = body.methods.inc (body.state), methods = body.methods}} as Counter; ⊳ sendinc : Counter -> Counter

Example More complex operations on counters can be implemented in terms of these two basic operations: add = c : Counter. sendinc (sendinc (sendinc c)); add : Counter -> Counter

Existential Types - Overview Typing and evaluation rules Introducing ADTs –Introducing ADTs –Introducing objects –Objects vs. ADTs Encoding existential types

Abstract type of counters ADT-style: counter values are elements of the underlying representation (i.e. simple numbers of type Nat ) object-style: each counter is a whole module, including not only the internal representation but also the methods. Type Counter stands for the whole existential type:  X. { state: X, methods: { get: X->Nat, inc: X->X}}

Stylistic advantages advantage of the object-style: since each object chooses its own representation and operations, different implementations of the same object can be freely intermixed advantage of the ADT-style: binary operations (i.e. operations that accept >= 2 arguments of the abstract type) can be implemented, contrary to objects

Binary operations and the object-style e.g. set objects type: NatSet = {  X, {state: X, methods: {empty: X, singleton: Nat->X, member: X->Nat->Bool, union: X->NatSet->X}}} cannot implement the method since it can have no access to the concrete representation of the second argument in reality, mainstream OO languages such as C++ and Java have a hybrid object model that allows binary operations (with the cost of restricting type equivalence)

Existential Types - Overview Typing and evaluation rules Introducing ADTs –Introducing ADTs –Introducing objects –Objects vs. ADTs Encoding existential types

Duality universal types: ∀ X.T is a value of type [S/X]T for all types S. existential types: ∃ X.T is a value of type [S/X]T for some type S. idea: exploit duality to encode existential types using universal types, using the equality: ∃ X.T = ¬ ∀ X.¬T def

Encoding encoding existential types using universal types: { ∃ X,T} = ∀ Y. ( ∀ X. T->Y) -> Y. operational view: a module is a value that gets a result type and a continuation, then calls the continuation to yield the final result A continuation is something that we can call and forget about because it does not return control to the caller. def

Encoding existential elimination given: let {X, x} = t 1 in t 2 where t 1 : ∀ Y. ( ∀ X. T -> Y) -> Y first apply to result type T 2 to get type ( ∀ X. T -> >T 2 ) -> T 2 : let {X, x} = t 1 in t 2 = t 1 T 2 … then apply to continuation of type ∀ X. T -> T 2 to get result type T 2 : let {X, x} = t 1 in t 2 = t 1 T 2 ( X. x : T.t 2 ) def

Encoding existential introduction given: {*S, t} as { ∃ X, T } we must use S and t to build a value of type ∀ Y. ( ∀ X. T -> Y) -> Y begin with two abstractions: {*S, t} as { ∃ X, T } = Y. f ( ∀ X. T -> Y) … apply f to appropriate arguments: first, supply S: {*S, t} as { ∃ X, T } = Y. f ( ∀ X. T -> Y). f S … then supply t of type S to get result type Y: {*S, t} as { ∃ X, T } = Y. f ( ∀ X. T -> Y). f S t def

Existential types - summary existential types are another form of polymorphism parametricity of existentials leads to representation independence trade-offs between ADTs and objects: –ADTs support binary operations, objects do not –objects support free intermixing of different implementations, ADTs do not existentials can be encoded using universal types

Module systems - overview The OCaml Module System Haskell Modules

The OCaml Module System three key parts: structures: are packaged environments. They consist of a group of core ML objects (values, types, exceptions) and can be manipulated as a single unit. module Name = struct implementation end signatures: are the ``types’’ of structures. Corresponding to each structure one can derive a signature which consists of the names of the objects in the structure and type information for the objects. module type Name = sig signature end functors: are mappings taking structures to structures. These are useful in specifying ``generic’’ packages that given any structure with a given signature can generate a new structure with some other signature. module Name = functor (ArgName : ArgSig) -> struct implementation end module Name (Arg : ArgSig) = struct implementation end

OCaml – Modules: Values Collection of named (mutually-recursive) values: module IntListSet = struct let empty = [] let add = fun i is -> i :: List.filter (fun j -> i != j) is let asList = fun is -> is end Access using dot-notation: val ok : int list let ok = IntListSet.add 1 IntListSet.empty Modules (structures) have types (signatures): module type INTLISTSET = sig val empty : int list val add : int -> int list -> int list val asList : int list -> int list end

OCaml – Modules: Types May also include named (data) types: module IntListSet = struct type t = int list let empty = [] let add = fun i is -> i :: List.filter (fun j -> i != j) is let asList = fun is -> is end val ok : IntListSet.t let ok = IntListSet.add 1 IntListSet.empty And named (nested) modules.

OCaml – Modules: Abstract Types May also include abstract types: module type INTSET = sig type t --- abstract val empty : t val add : int -> t -> t val asList : t -> int list end module IntSet : INTSET = struct type t = int list --- concrete let empty = [] let add = fun i is -> i :: List.filter (fun j -> i != j) is let asList = fun is -> is end val ok : IntListSet.t let ok = IntListSet.add 1 IntListSet.empty val fail : int list let fail = IntListSet.add 1 [2] -- type error: incompatible types int list and IntListSet.t

OCaml – Modules: Separate Compilation Top-level modules may correspond with compilation units. True separate compilation is possible if each top-level module is accompanied by a top-level signature. intSet.mli: type t val empty : t val add : int -> t -> t val asList : t -> int list intSet.ml: type t = int list let empty = [] let add = fun i is -> i :: List.filter (fun j -> i != j) is let asList = fun is -> is Compiler never needs to look at intSet.ml when compiling other modules – only intSet.mli.

OCaml – Modules: Value Parameterisation May be parameterised by the values within other modules(functors): module type INTORD = sig val less : int -> int -> bool end module MkIntBinTreeSet = functor (IntOrd : INTORD) -> struct type t = Leaf | Node of bintree * int * bintree let empty = Leaf let rec add = fun i t -> match t with | Leaf -> Node (Leaf, i, Leaf) | Node (l, j, r) -> if IntOrd.less i j then Node (add i l, j, r) else... let rec asList = … end module IntOrd = struct let less = (<) end module IntBinTreeSet = MkIntBinTreeSet IntOrd

OCaml – Modules: Type Parameterisation May be parameterised by the types within other modules: module type ORD = sig type t val less : t -> t -> bool end module MkBinTreeSet = functor (Ord : ORD) -> struct type t = Leaf | Node of bintree * Ord.t * bintree let empty = Leaf let rec add = fun x t -> … let rec asList = … end Module IntOrd = struct type t = int let less = (<) end Module IntBinTreeSet = MkBinTreeSet IntOrd

OCaml – Modules: Type Sharing Interaction of –Separate compilation, and –Parameterisation by types Requires special support m.mli: module type SET = sig type elt type t val empty : t val add : elt -> t -> t val asList : t -> elt list end module type MKBINTREESET = functor (Ord : ORD) -> SET module MkBinTreeSet : MKBINTREESET

OCaml - Modules: Type Sharing Interaction of –Separate compilation, and –Parameterisation by types Requires special support x.ml: module IntOrd = struct type t = int let less = (<) end module IntBinTreeSet = M.MkBinTreeSet IntOrd val fail : IntBinTreeSet.t let fail = IntBinTreeSet.add 1 IntBinTreeSet.empty -- type error: incompatible types int and IntBinTreeSet.elt

OCaml – Modules: Type Sharing (2) We need to capture the sharing of types between the argument and result of the functor MkBinTreeSet. m.mli: module type SET = sig type elt type t val empty : t val add : elt -> t -> t val asList : t -> elt list end module type MKBINTREESET = functor (Ord : ORD) -> (SET with type elt = Ord.t) module MkBinTreeSet : MKBINTREESET

OCaml – Modules: Type Sharing (2) We need to capture the sharing of types between the argument and result of the functor MkBinTreeSet. x.ml: module IntOrd = struct type t = int let less = (<) end module IntBinTreeSet = M.MkBinTreeSet IntOrd val fail : IntBinTreeSet.t let fail = IntBinTreeSet.add 1 IntBinTreeSet.empty -- ok, since int = IntOrd.t = IntBinTreeSet.elt

The Haskell module system ``Haskell has a very simple module system -- a flat module namespace with the ability to import and export various entities, hide names, and specify that module qualification (M.x) is required. It also provides support for abstract data types by allowing one to export a data type without its constructors. ’’ -- Paul Hudak, 1998

The Haskell Module System Module headers module Ant where data Ants = … anteater x = … The convention for file names is that a module Ant resides in the Haskell file Ant.hs or Ant.lhs.

The Haskell Module System Importing a module module Bee where import Ant beeKeeper = … This means that the visible definitions from Ant can be used in Bee. By default the visible definitions in a module are those which appear in the module itself module Cow where import Bee

The Haskell Module System The main module Each system of modules should contain a top-level module called Main, which gives a definition to the name main. Note that a module with no explicit name is treated as Main.

The Haskell Module System Export controls –we might wish not to export some auxiliary functions. –We perhaps want to export some of the definitions we imported from other modules. We can control what is exported by following the name of the module with a list of what is to be exported: module Bee ( beeKeeper, Ants(..), anteater ) where … We follow the type name with (..) to indicate that the constructors of the type are exported with the type itself; if this is omitted, then the type acts like an abstract data type.

The Haskell Module System We can also state that all the definitions in a module are to be exported: module Bee ( beeKeeper, module Ant ) where … or equivalently module Bee ( module Bee, module Ant ) where … The simpler header module Fish where is equivalent to module Fish ( module Fish ) where

The Haskell Module System Import controls we can control how objects are to be imported: import Ant ( Ants(..) ) stating that we want just the type Ants we can alternatively say which names we wish to hide: import Ant hiding ( anteater )

The Haskell Module System Suppose that in our module we have a definition of bear, and also there is an object named bear in the module Ant. –Use the qualified name Ant.bear for the imported object, reserving bear for the locally defined one. To use qualified names we should make the import thus: import qualified Ant Give a local name Insect to a imported module Ant: import Insect as Ant

The Haskell Module System The standard prelude –Prelude.hs is implicitly imported into every module. –Modify this import, perhaps hiding one or more bindings thus: module Eagle where import Prelude hiding (words) –A re-definition of a prelude function cannot be done ``invisibly’’. We have explicitly to hide the import of the name that we want to re-define. –If we also wish to have access to the original definition of words we can make a qualified import of the prelude, import qualified Prelude and use the original words by writing its qualified name Prelude.words.

Example - Stacks A stack implemented as an ADT module can be defined as follows: module Stack(Stack,push,pop,top,emptyStack,stackEmpty) where push :: a -> stack a -> Stack a pop :: Stack a -> Stack a top :: Stack a -> a emptyStack :: Stack a stackEmpty :: Stack a -> Bool

Example - Stacks A first implementation can be done using a user-defined type: data Stack a = EmptyStk | Stk a (Stack a) push x s = Stk x s pop EmptyStk = error ``pop from an empty stack’’ pop (Stk _ s) = s top EmptyStk = error ``top from an empty stack’’ top (Stk x _) = x emptyStack = EmptyStk stackEmpty EmptyStk = True stackEmpty _ = False

Example - Stacks Another possible implementation can be obtained using the predefined list data structure because push and pop are similar to the (:) and tail operations. newtype Stack a = Stk [a] push x (Stk xs) = Stk (x:xs) pop (Stk []) = error ``pop from an empty stack’’ pop (Stk (_:xs)) = Stk xs top (Stk []) = error ``top from an empty stack’’ top (Stk (x:_)) = x emptyStack = Stk [] stackEmpty (Stk []) = True stackEmpty (Stk _ ) = False

Thank You Very Much! References: Types and Programming Languages Abstract Types Have Existential Type First-Class Modules for Haskell