CS3110 Spring 2016 Lecture 6: Modules

Similar documents
Some Advanced ML Features

G Programming Languages - Fall 2012

Modular Programming. Prof. Clarkson Fall Today s music: "Giorgio By Moroder" by Daft Punk

Modules and Representation Invariants

CS 11 Ocaml track: lecture 2

CS Lecture 6: Map and Fold. Prof. Clarkson Fall Today s music: Selections from the soundtrack to 2001: A Space Odyssey

CS Lecture 6: Map and Fold. Prof. Clarkson Spring Today s music: Selections from the soundtrack to 2001: A Space Odyssey

CMSC 330: Organization of Programming Languages. Functional Programming with Lists

Lists. Michael P. Fourman. February 2, 2010

CS 11 Ocaml track: lecture 4. Today: modules

Introduction to OCaml

SNU Programming Language Theory

CSE 341 Section 5. Winter 2018

Two approaches to writing interfaces

G Programming Languages Spring 2010 Lecture 8. Robert Grimm, New York University

Programming Language Concepts, CS2104 Lecture 7

Programming in Haskell Aug-Nov 2015

FUNCTIONAL PROGRAMMING

Reference Cells. Example

CMSC 330: Organization of Programming Languages. Functional Programming with Lists

CS Lectures 2-3. Introduction to OCaml. Polyvios Pratikakis

CSE 130, Fall 2005: Final Examination

Abstraction Functions and Representation Invariants

Programming Languages and Techniques (CIS120)

Map and Fold. Prof. Clarkson Fall Today s music: Selections from the soundtrack to 2001: A Space Odyssey

CSCI-GA Scripting Languages

Introduction to ML. Mooly Sagiv. Cornell CS 3110 Data Structures and Functional Programming

MPRI course 2-4 Functional programming languages Exercises

OCaml Style Guide Fall 2017

Modules and Abstract Data Types

CS 321 Programming Languages

CS3110 Spring 2017 Lecture 9 Inductive proofs of specifications

Wentworth Institute of Technology COMP201 Computer Science II Spring 2015 Derbinsky. Stacks and Queues. Lecture 11.

CMSC 330: Organization of Programming Languages. Objects and Abstract Data Types

School of Computer Science

CSE 341 : Programming Languages Midterm, Spring 2015

CSE 143. Lecture 4: Stacks and Queues

COSE212: Programming Languages. Lecture 4 Recursive and Higher-Order Programming

Data Abstraction and Specification of ADTs

CS3110 Spring 2017 Lecture 7 Specifications using types continued

CPSC 221: Algorithms and Data Structures Lecture #1: Stacks and Queues

CSE 130, Fall 2006: Final Examination

Introduction to ML. Mooly Sagiv. Cornell CS 3110 Data Structures and Functional Programming

CS109B ML Notes for the Week of 5/8/95. Structures. ML's way of encapsulating concepts such as a data. structure and functions that operate on it.

Chapter 6 Abstract Datatypes

Week 6. Data structures

Introduction to ML. Mooly Sagiv. Cornell CS 3110 Data Structures and Functional Programming

CS3110 Spring 2017 Lecture 10 a Module for Rational Numbers

Haskell: Lists. CS F331 Programming Languages CSCE A331 Programming Language Concepts Lecture Slides Friday, February 24, Glenn G.

l Determine if a number is odd or even l Determine if a number/character is in a range - 1 to 10 (inclusive) - between a and z (inclusive)

CMSC Introduction to Algorithms Spring 2012 Lecture 7

Lecture 7: Data Structures. EE3490E: Programming S1 2017/2018 Dr. Đào Trung Kiên Hanoi Univ. of Science and Technology

Algorithms and Data Structures

Tuples. CMSC 330: Organization of Programming Languages. Examples With Tuples. Another Example

IT 4043 Data Structures and Algorithms. Budditha Hettige Department of Computer Science

CS 565: Programming Languages. Spring 2008 Tu, Th: 16:30-17:45 Room LWSN 1106

OCaml Data CMSC 330: Organization of Programming Languages. User Defined Types. Variation: Shapes in Java

EPITA - Practical Programming 06 - Advanced Module Usage

CPSC 221: Algorithms and Data Structures ADTs, Stacks, and Queues

-The Hacker's Dictionary. Friedrich L. Bauer German computer scientist who proposed "stack method of expression evaluation" in 1955.

Gain familiarity and practice with folding and mapping. The following supplementary materials may be helpful in completing this assignment:

Parametricity. Leo White. February Jane Street 1/ 70

Discussion 2C Notes (Week 3, January 21) TA: Brian Choi Section Webpage:

Functional Programming. Introduction To Cool

List Functions, and Higher-Order Functions

Sample Exam; Solutions

CS3110 Spring 2017 Lecture 8 Specifications continued

Linked Structures. See Section 3.2 of the text.

8. Fundamental Data Structures

Programming in Standard ML: Continued

CIS 120 Midterm I October 2, Name (printed): Username (login id):

CMSC 330: Organization of Programming Languages

.:: UNIT 4 ::. STACK AND QUEUE

Background. CMSC 330: Organization of Programming Languages. Useful Information on OCaml language. Dialects of ML. ML (Meta Language) Standard ML

csci 210: Data Structures Stacks and Queues

Programming Languages and Compilers (CS 421)

LIFO : Last In First Out

CS24 Week 4 Lecture 2

CMSC 330, Fall 2013, Practice Problems 3

Programming Languages and Techniques (CIS120)

double d0, d1, d2, d3; double * dp = new double[4]; double da[4];

Reasoning About Programs Panagiotis Manolios

Pattern Matching and Abstract Data Types

JSJS - Project Proposal

Stacks and Queues. Introduction to abstract data types (ADTs) Stack ADT. Queue ADT. Time permitting: additional Comparator example

cs Java: lecture #6

Data structure and algorithm in Python

CS350 - Exam 1 (100 Points)

CS 206 Introduction to Computer Science II

Modular Module Systems: a survey

Tail Calls. CMSC 330: Organization of Programming Languages. Tail Recursion. Tail Recursion (cont d) Names and Binding. Tail Recursion (cont d)

CS3110 Spring 2016 Lecture 21: Elements of Type Theory

Announcements. CSCI 334: Principles of Programming Languages. Lecture 5: Fundamentals III & ML

You must include this cover sheet. Either type up the assignment using theory4.tex, or print out this PDF.

Algorithms and Data Structures

Programming Languages. Example 5. Example 4. CSE 130 : Fall type, can reuse code for all types! let rec cat l = match l with

CMSC 330: Organization of Programming Languages

Principles of Functional Programming

More Lambda Calculus and Intro to Type Systems

L3 Programming September 19, OCaml Cheatsheet

Transcription:

CS3110 Spring 2016 Lecture 6: Modules Modules are the O part of OCaml, influenced by objects in Java. We see the evolution: Classic ML, CambridgeML (CAML), and finally OCaml. David McQueen is writing the history of this evolution. Motivations 1. Managing specifications and code for large projects, local recoding does not impact other modules, can discuss the system structure and design. 2. Enriching the type system with module signatures and functors. Module structures are a new kind of object, like a tuple. The type theories of proof assistants are richer and motivate PL extensions. We will look briefly at the way rich type systems handle these concepts. 3. Connections to the area of Mathematical Knowledge Management (MKM) as conducted by modern proof assistants and theorem provers. The MKM efforts introduce a classification of mathematical topics. We see this in our refernce to the ring of integers and the field of rationals and the field of reals. nat for Natural numbers int exts nat, provides structure of a ring. rat exts int, provides structure of a field reals exts rat and provides a field complex exts reals and provides a field Types corresponding to propositional logic: 1

Logical types: α β for & (and) α β for implies Lα Rβ for or α β α is a valid logical principle, so are: α Lα Rβ α β β α, etc. There are two aspects to modules: The type part, called the signature. module type Name = sig... defintions The value part, called the structure. module type modname = struct... implementation Example from PS2: module type N = sig type t val zero : t val succ : t -> t val eq : t -> t -> bool The centerpiece of PS2 is a module for the rational numbers, Q. module type Q = sig defs We have provided the signature, your job is to build the structure, called Rational. 2

Functional stacks and queues, dictionaries, fractions Functional data structures In this recitation, we look at examples of structures and signatures that implement data structures. We show that stacks and queues can be implemented efficiently in a functional style. What is a functional stack, or a functional queue? It is a data structure for which the operations do not change the data structure, but rather create a new data structure, with the appropriate modifications, instead of changing it in-place. In imperative languages, data operations generally support destructive update destructive in the sense that after the update is done, the original data structure is gone. Functional abstractions supportnondestructive updates: the original value is still around, unmodified, so code that was using it is unaffected. For efficiency, it is important to implement nondestructive updates not by creating an entirely new data structure, but by sharing as much as possible with the original data structure.

Stacks Recall a stack: a last-in first-out (LIFO) queue. Just like lists, the stack operations fundamentally do not care about the type of the values stored, so it is a naturally polymorphic data structure. Here is a possible signature for functional stacks: module type STACK = sig (* A stack of elements of type 'a. We write to * denote a stack whose top element is a1, with successive * elements a2, a3,...an. *) type 'a stack exception EmptyStack (* The empty stack. *) val empty : 'a stack (* Whether this stack is empty. *) val isempty : 'a stack -> bool (* Returns a new stack with x pushed onto the top. *) val push : ('a * 'a stack) -> 'a stack (* Returns a new stack with the top element popped off. *) val pop : 'a stack -> 'a stack (* The top element of the stack. *) val top : 'a stack -> 'a (* map(f) maps one stack into a corresponding stack, using f. *) val map : ('a -> 'b) -> 'a stack -> 'b stack (* app(f) applies f to every element of the stack, top to bottom. *) val app : ('a -> unit) -> 'a stack -> unit This signature specifies a parameterized abstract type for stack. Notice the type variable'a. The signature also specifies the empty stack value, and functions to check if a stack is empty, and to perform push, pop and top operations on the stack. Moreover, we specify functions map and app to walk over the values of the stack.

We also declare an exception EmptyStack to be raised by top and pop operations when the stack is empty. Here is the simplest implementation of stacks that matches the above signature. It is implemented in terms of lists. module Stack : STACK = struct type 'a stack = 'a list exception EmptyStack let empty : 'a stack = [] let isempty (l : 'a stack) : bool = l = [] let push ((x : 'a), (l : 'a stack)) : 'a stack = x :: l let pop (l : 'a stack) : 'a stack = match l with [] -> raise EmptyStack x :: xs -> xs let top (l : 'a stack) : 'a = match l with [] -> raise EmptyStack x :: xs -> x let map (f : 'a -> 'b) (l : 'a stack) : 'b stack = List.map f l let app (f : 'a -> unit) (l : 'a stack) : unit = List.iter f l Up until now, we have been defining exceptions solely in order to raise them and interrupt the executing program. Just like in Java, it is also possible to catch exceptions, which is termed 'handling an exception' in OCaml. As an example, consider the following example. In the above code, we have implemented top and pop respectively as functions that return the first element of the list and the rest of the list. OCaml already defines functions to do just that, namely List.hd and List.tl (for head and tail). The function hd takes a list as argument and returns the first element of the list, or raises the exception Failure if the list is empty. Similarly for tl. One would like to simply be able to write in Stack: let top (l : 'a stack) : 'a = List.hd l let pop (l : 'a stack) : 'a stack = List.tl l

However, if passed an empty stack, top and pop should raise the EmptyStackexception. As written above, the exception Failure would be raised. What we need to do is intercept (or handle) the exception, and raise the right one. Here's one way to do it: let top (l : 'a stack) : 'a = try List.hd l with Failure _ -> raise EmptyStack let pop (l : 'a stack) : 'a stack = try List.tl l with Failure _ -> raise EmptyStack The syntax for handling exceptions is as follows: try e with exn -> e' where e is the expression to evaluate, and if e raises an exception that matches exn, then expression e' is evaluated instead. The type of e and e' must be the same. Signatures To successfully develop large programs, we need more than the ability to group related operations together in a module. We need to be able to use the compiler to enforce the separation between different modules, which prevents bad things from happening. Signatures are the mechanism that enforces this separation. Signature declarations that have the following syntax: module type SIGNAME = sig definitions By convention, the signature name SIGNAME is all in capital letters. The definitions of a signature declare a set of types and values that any module implementing it must provide. The definitions of a signature may be type definitions, val definitions to define the type signature of a name, and exception definitions to specify exceptions that module can raise. A module that implements a particular signature specifies the name of that signature in its definition, after the module name and separated by a : as with types. The signature must be defined before the module is defined. module ModuleName : SIGNAME = struct implementation

A module that implements a signature must specify concrete types for the abstract types in the signature and provide all the declarations in the signature. Only the abstract types are accessible outside the module, unless the signature exposes the definition. Only declarations in the signature are accessible outside of the module (for instance functions defined in the implementation but not in the signature are not accessible). For example, here is a signature for a simple set data abstraction, together with two implementations of that interface using lists:

(* Set data abstraction with union and intersection *) module type SET = sig type 'a set val empty : 'a set val mem : 'a -> 'a set -> bool val add : 'a -> 'a set -> 'a set val rem : 'a -> 'a set -> 'a set val size: 'a set -> int val union: 'a set -> 'a set -> 'a set val inter: 'a set -> 'a set -> 'a set (* Implementation of sets as lists with duplicates *) module Set1 : SET = struct type 'a set = 'a list let empty = [] let mem = List.mem let add x l = x :: l let rem x = List.filter ((<>) x) let rec size l = match l with [] -> 0 h :: t -> size t + (if mem h t then 0 else 1) let union l1 l2 = l1 @ l2 let inter l1 l2 = List.filter (fun h -> mem h l2) l1 (* Implementation of sets as lists without duplicates *) module Set2 : SET = struct type 'a set = 'a list let empty = [] let mem = List.mem (* add checks if already a member *) let add x l = if mem x l then l else x :: l let rem x = List.filter ((<>) x) let size = List.length (* size is just length if no duplicates *) let union l1 l2 = (* check if already in other set *) List.fold_left (fun a x -> if mem x l2 then a else x :: a) l2 l1 let inter l1 l2 = List.filter (fun h -> mem h l2) l1

Queues Let us write an example more interesting than stacks. After all, from the above, one can see that they are just lists. Consider the queue data structure, a first-in first-out data structure. Again, we consider functional queues. Here is a possible signature: module type QUEUE = sig type 'a queue exception EmptyQueue val empty : 'a queue val isempty : 'a queue -> bool val enqueue : ('a * 'a queue) -> 'a queue val dequeue : 'a queue -> 'a queue val front : 'a queue -> 'a val map : ('a -> 'b) -> 'a queue -> 'b queue val app : ('a -> unit) -> 'a queue -> unit The simplest possible implementation for queues is to represent a queue via two stacks: one stack A on which to enqueue elements, and one stack B from which to dequeue elements. When dequeuing, if stack B is empty, then we reverse stack A and consider it the new stack B.

Here is an implementation for such queues. It uses the stack structure Stack, which is rebound to the name S inside the structure to avoid long identifier names. module Queue : QUEUE = struct module S = Stack type 'a queue = ('a S.stack * 'a S.stack) exception EmptyQueue let empty : 'a queue = (S.empty, S.empty) let isempty ((s1, s2) : 'a queue) = S.isEmpty s1 && S.isEmpty s2 let enqueue ((x : 'a), ((s1, s2) : 'a queue)) : 'a queue = (S.push (x, s1), s2) let rev (s : 'a S.stack) : 'a S.stack = let rec loop ((prev : 'a S.stack), (curr : 'a S.stack)) : 'a S.stack = if S.isEmpty prev then curr else loop (S.pop prev, S.push (S.top prev, curr)) in loop (s, S.empty) let dequeue ((s1, s2) : 'a queue) : 'a queue = if S.isEmpty s2 then try (S.empty, S.pop (rev s1)) with S.EmptyStack -> raise EmptyQueue else (s1, S.pop s2) let front ((s1, s2) : 'a queue) : 'a = if (S.isEmpty s2) then try S.top (rev s1) with S.EmptyStack -> raise EmptyQueue else S.top s2 let map (f : 'a -> 'b) ((s1, s2) : 'a queue) : 'b queue = (S.map f s1, S.map f s2) let app (f : 'a -> unit) ((s1, s2) : 'a queue) : unit = S.app f s2; S.app f (rev s1) We learned about folding last week. In the above implementation, the stack reversal could have been done using fold. However, since the Stack module does not specify a fold operation, and the implementation of the Stack as a list is hidden from the Queue module, we need something more. The Stack signature should specify a fold operation that will help its users to iterate over its elements.