CSE 130 : Fall 2007 Programming Languages Previously: Polymorphism enables Reuse • Can reuse generic functions: map :’a * ’b → ’b * ’a filter: (’a → bool * ’a list) → ’a list rev: ’a list → ’a list length: ’a list → int swap: ’a * ’b → ’b * ’a Lecture 9: Modules and Signatures sort: (’a → ’a → bool * ’a list) → ’a list Ranjit Jhala UC San Diego compose: (’a → ’b * ’c → ’b) → ’c → ’a • If function (algorithm) is “independent” of type, can reuse code for all types ! Today: Divide-Conquer Programming • We’ve seen very small programs so far – 4 yr education chopped into quarters • Typical software is thousands, millions LOC Rational Numbers • Say you want a software package that uses rational numbers heavily • Remember rationals ? – Ratio of two integers: m/n • How can a PL help to manage complexity ? – Only one technique humans know about • Well, seems simple enough: – Use a pair of integers to represent rationals! – OCaml type: int * int Rational Numbers • Well, every pair of isn’t a rational… – (5,0) ? – 2nd elem “denominator” must not be zero Many functions related to type module Name = struct bindings Pack into a module • Types, Values, Exceptions bound in Name are: – Name.t, Name.x, Name.f • How do we print a rational ? • Bind module to diff name • When are two rationals “equal” ? – (6,4) and (30,20) ? – Need to normalize rationals – But need to fix to_string end module NewName = Rational;; NewName.add;; … NewName.to_string;; … 1 Many functions related to type module Name = struct bindings Pack into a module • Types, Values, Exceptions bound in Name are: Many functions related to type structure Name = struct bindings • Can pack into a module • Variables, functions, exceptions “bound” in Name are: – Name.x , Name.f – Name.t, Name.x, Name.f • Bind module to diff name • Open module • Can open module – Everything comes tumbling out end open Rational;; add;; … to_string;; … Information Hiding • Prevent the client from passing in any pair – Hide implementation from client end • Not much better than diff files! – Whats the problem ? Specify Interfaces by Signatures module type SIGNAME = sig type-bindings end • Control usage by restricting the interface • Interface: what any client needs to know module Name: SIGNAME = struct bindings end Signature = Type of a module • Specifies what module provides – types – values and their types – exceptions and their types Name : SIGNAME • Name implements SIGNAME • Code using Name knows only the entities in signature Implementing a Signature Hiding • ML checks if Name:SIGNAME • SIGNAME can hide bindings of Name – Name must contain all the bindings in SIGNAME – May and usually will, have more bindings module type RATIONAL = sig exception BadRational type rational = … val from_int : … val to_string : … val add : … end module Rational = sig exception BadRational type rational = int * int let gcd (x,y) = … let normalize (x,y) = … let from_int (x,y) = … let to_string = … let add r1 r2 = … end – Bindings not in SIGNAME are hidden (private) – Outside Name, i.e. to clients, invisible module type RATIONAL = sig exception BadRational type rational = … val from_int : … val to_string : … val add : … end module Rational = sig exception BadRational type rational = int * int let gcd (x,y) = … let normalize (x,y) = … let from_int (x,y) = … let to_string = … let add r1 r2 = … end 2 Still doesn’t solve problem Abstracting Types Inept client: • SIGNAME can hide type definitions of Name Rational.add (2,3) (0,0);; – Type defs not in SIGNAME are abstract – Cannot create values of abs. type outside Name Loops forever… Solution: Make types abstract • Client knows “name” of type, not implementation! • Cannot directly access or create values of abs type • Restrict the interface to enforce invariants Benefits of hiding information 1. Forces divide and conquer: – Restrict interactions – Can reason about the code in small pieces • Instead of a massive jungle • Know exactly how clients can use structure 2. Plug-and-Play: – Can easily replace implementation (as long as signature matches) – Doesn’t break clients – Separate compilation module type RATIONAL = sig exception BadRational type rational = int*int val from_int : … val to_string : … val add : … end module Rational = sig exception BadRational type rational = int * int let gcd (x,y) = … let normalize (x,y) = … let from_int (x,y) = … let to_string = … let add r1 r2 = … end Another example: Stack module Stack = struct type 'a stack = Empty | Push of ('a * 'a stack) exception EmptyStack let newstack () = Empty let push s e = Push (e,s) let pop s = match s with Empty -> raise EmptyStack | Push(x,_) -> x let is_empty s = match s with Empty -> true | _ -> false let rec toList s = match s with Empty -> [] | Push(top,bot) -> top :: (toList bot) let to_string f s = String.concat "," (List.map f (toList s)) end module type NE1 = sig type name = string type expr type nExpr = name*expr val makeExpr:string*(int*int->int)->(nExpr*nExpr) -> nExpr val constExpr : int -> namedExpr val eval : namedExpr -> int end module type NE2 = sig type name type expr type nExpr = name*expr val makeExpr:string*(int*int->int)->(nExpr*nExpr) -> nExpr val constExpr : int -> namedExpr val eval : namedExpr -> int end module type NE2 = sig type name type expr type nExpr = name*expr val makeExpr:string*(int*int->int)->(nExpr*nExpr) -> nExpr val constExpr : int -> namedExpr val eval : namedExpr -> int end module type NE3 = sig type name type expr type nExpr val makeExpr:string*(int*int->int)->(nExpr*nExpr) -> nExpr val constExpr : int -> namedExpr val eval : namedExpr -> int end 3 Another example: PA4 module type EXPR = sig type 'a oper = string * ('a list -> 'a) type 'a expr val buildF : 'a oper -> 'a expr list -> 'a expr val buildX : int -> 'a expr val exprToString : 'a expr -> string val eval_fn : 'a expr -> 'a list -> 'a end Moral “Perfection is achieved, not when there is something left to add, but when there is nothing left to take away.” - Antoine de St. Exupery, “The little prince” PA4: Write two implementations of the above signature. i.e. implement the expr type in two ways … Client, i.e. art.ml, remains the same Key components of a lang • Units of computation Deconstructing OCaml • Types The anatomy of a Programming language • Memory model In OCaml Units of computation 4 In OCaml In Java/Python • Expressions that evaluate to values • Everything is an expression – – – – – – int, bool, real if-then-else let-in match fun x -> x+1 e1 e2 In Java/Python In Prolog • Store and update commands • Message sends In Prolog • Logical facts • Inference rules Types Mexican(CARNITAS) Food(CARNITAS) “Fact” “Fact” Mexican(X) Æ Food(X) ⇒ Delicious(X) Delicious(CARNITAS) “Rule” “Fact” 5 Types In OCaml: Static typing • Used to classify things created by the programmer • Types are assigned statically at compile time • Classification used to check what can be done with/to those things • Without computing values • Rules state when expressions are typecorrect Γ ⊢ e1:T1 → T2 Γ ⊢ e2: T1 Γ ⊢ e1 e2 : T2 In OCaml: Static typing In Python: Dynamic typing • How can one reuse code for different types? • Types assigned to values/objects as they are computed, ie: dynamically – parametric types: ‘a * ‘b -> ‘b * ‘a – implicit forall • Type “discovered” (inferred) automatically from code • Before an operation is performed, check that operands are compatible with operation – less burden on the programmer In Python: Dynamic typing Dynamic vs. Static, OO vs. Func • More programs are accepted by compiler ⇒ More flexible [1, “abc”, 1.8, [ “efg”, 20]] let x = if b then 1 else “abc” let y = if b then x + 1 else x ^ “efg” Statically typed Dynamically typed OO Functional 6 Dynamic vs. Static, OO vs. Func OO Statically typed Dynamically typed Java Python, Smalltalk, Diesel Polymorphism • PL that is polymorphic + dynamically typed ? • Every dynamically typed PL is polymorphic – functions simply work on any datatype that can be operated on at runtime • Explicit polymorphism in statically typed PL Functional Ocaml, Haskell Lisp/Scheme – assign at compile time a general polymorphic type Data model in functional langs • Environment of bindings (phonebook) Memory/Data model aka: what do variables refer to? • Never change a binding – add new bindings at the end of phonebook Data model in functional langs Data model in OO langs • Variables = names in phonebook • Most recent entry looked up during eval • Variables are named cells in memory • Can change them by assigning into them Closures: • Environment “frozen” in function value • Variables = names of objects on the heap – behavior of function cannot be changed later – easier reasoning, debugging • x = x + 10 7 Data model in Prolog • Variables = unknowns to solve for Mexican(CARNITAS) Final words on functional programming Food(CARNITAS) ∀ X Mexican(X) Æ Food(X) ⇒ Delicious(X) Delicious(Y)? Q: What is delicious? A: CARNITAS! What’s the point of all this? Advantages of functional progs • Functional programming more concise “one line of lisp can replace 20 lines of C” (quote from http://www.ddj.com/dept/architect/184414500?pgno=3) • Recall reverse function in OCaml: let reverse = fold (::) [];; • How many lines in C, C++? Don’t be fooled Can better reason about progs • PA made you do certain things using fold in order to force you to think about it, fold may not be the easiest way to do it. • No side effects. Call a function twice with same params, produces same value • But many cases where map and fold make life A LOT EASIER. • As a result, computations can be reordered more easily • They can also be parallelized more easily 8 An example: google What is MapReduce? • To make their data processing scale better, google devised a programming model called MapReduce • Programming model based on map, fold • Used widely inside google (over 800 instances of it used in the google source tree in late 2004, and still counting) • Because of the functional aspect So what? Remember • Form the authors: “Inspired by similar primitives in LISP and other languages” • The next time you use google, think of how functional programming has inspired key technology behind their engine http://labs.google.com/papers/mapreduce-osdi04-slides/index-auto-0003.html • The point is this: programmers who only know Java/C/C++ would probably not have come up with this idea – apply a function on each data element (map) – aggregate the results (fold) – easily parallelized ⇒ highly scalable • And of course: “Free your mind” -Morpheus • Many other similar examples in industry 9