15 150 fall 2020 lecture 15
play

15-150 Fall 2020 Lecture 15 Modular programming (part 1) Stephen - PowerPoint PPT Presentation

15-150 Fall 2020 Lecture 15 Modular programming (part 1) Stephen Brookes problem of the day Write an ML function factzeros : int -> int such that for all n>0, 5! = 120 factzeros n = factzeros 5 = 1 the number of zeros on the end of


  1. 15-150 Fall 2020 Lecture 15 Modular programming (part 1) Stephen Brookes

  2. problem of the day Write an ML function factzeros : int -> int such that for all n>0, 5! = 120 factzeros n = factzeros 5 = 1 the number of zeros on the end of n! (in decimal)

  3. modularity principle • A large program should be organized as a collection of small components • manageable size • easy to maintain • Give an interface for each component • others should rely only on this Motivation: to facilitate separate development

  4. advantages • Identify and isolate common situations signature ORDERED = sig “ordered types” type t val compare : t * t -> order end structure Integers : ORDERED = struct integers, type t = int fun compare (x, y) = with usual order if x<y then LESS else if y<x then GREATER else EQUAL end Ordered types are everywhere

  5. advantages • We may want to hide internal details helper functions, type implementation • Do this by leaving them out of the signature or by ascribing the signature opaquely • You can ascribe a signature to a structure transparently or opaquely… structure Integers : ORDERED = : means transparent struct …

  6. advantages • With well chosen signatures, can organize code into separate fragments • A “module” is a combination of a signature + a structure that implements it • Modules can be designed separately by different programmer teams, or assembled in any order, then combined

  7. language support Signatures • interfaces Structures • implementations Functors • ways to combine structures...

  8. signatures A signature is a collection of names with specified types signature SIGNAME = sig . names for types , . functions , . exceptions end

  9. a signature A type named integer A function rep : int -> integer A function display : integer -> string A function add : integer * integer -> integer signature ARITH = sig A function mult : integer * integer -> integer type integer val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer end

  10. just an interface • Just d eclaring this signature doesn’t create any types, values, exceptions… • To implement it we must define a structure • Defining a structure with this signature will create instances of the types, values and exceptions in the signature one signature may have many implementations

  11. an implementation signature ARITH = sig type integer val rep : int -> integer structure Ints = val display : integer -> string val add : integer * integer -> integer struct val mult : integer * integer -> integer end type integer = int fun rep n = n fun display n = Int.toString n fun add(x, y) = x + y fun mult(x, y) = x * y end This structure implements ARITH and defines integer as the type int

  12. result signature ARITH = sig type integer val rep : int -> integer val display : integer -> string val add : integer * integer -> integer val mult : integer * integer -> integer ML says end structure Ints : whoa! sig type integer = int val rep : 'a -> 'a val display : int -> string val add : int * int -> int val mult : int * int -> int end

  13. Ints implements ARITH • a type named integer • values rep, display, add, mult of types consistent with ARITH structure Ints : signature ARITH = sig sig type integer = int type integer val rep : ’a -> ’a val rep : int -> integer val display : int -> string val display : integer -> string val add : integer * integer -> integer val add : int * int -> int val mult : integer * integer -> integer val mult : int * int -> int end end rep is OK because integer = int and int -> integer is an instance of ’a -> ’a

  14. ascription (transparent) ascribing the signature ARITH structure Ints : ARITH = constrains what struct the structure provides type integer = int fun rep n = n fun display n = Int.toString n fun add(x, y) = x + y fun mult(x, y) = x * y end ML says structure Ints : ARITH

  15. ascription requirements Ints : ARITH • For every type named in ARITH, Ints must have a type definition • For every value named in ARITH, Ints must have a suitable declaration same name , at least as general type these rules must be obeyed by implementers

  16. ascription requirements Ints : ARITH • Ints defines integer as int • Ints declares the values named in ARITH, with types at least as general as required, given that integer is int add : integer * integer -> integer add : int * int -> int rep : int -> integer rep : int -> int

  17. what can go wrong signature CIRCLE = sig val radius : real val center : real * real end structure Square : CIRCLE = struct val side = 3.0 val center = (0.0, 0.0) end > Error: unmatched value specification: radius

  18. ascription constraints Ints : ARITH • Users of Ints can only use data named in ARITH • Users of Ints “know” that integer is int • Users of Ints can call rep, add, mult, display and can do arithmetic on values of type integer 40 + rep 2 =>* 42 : int this rule is imposed on all clients or users

  19. open opening a structure reveals the types and values in its signature - open Ints; - add(rep 2, rep 3); ML says val it = 5 : integer opening Ints type integer = int val rep : int -> integer val add : integer * integer -> integer val mult : integer * integer -> integer val display : integer -> string

  20. transparency - structure Ints : ARITH = struct type integer = int; … … end - open Ints; opening Ints type integer = int …….

  21. transparency • Users of a structure with a transparent signature can see type information specified inside the structure type integer = int • The REPL will allow things like fun double(x:integer):integer = mult(x, 2) ML says val double = fn - : integer -> integer

  22. transparency • Users of a structure with a transparent signature can see type information specified inside the structure type integer = int • The REPL will allow things like fun double(x:integer):integer = mult(x, 2) ML says val double = fn - : integer -> integer This may seem fine, but knowledge is power, so be careful

  23. transparency • Users of a structure with a transparent signature can see type information specified inside the structure type integer = int • The REPL will allow things like fun double(x:integer):integer = mult(x, 2) ML says val double = fn - : integer -> integer This may seem fine, but knowledge is power, so be careful We’ll soon see an example where it makes better sense to hide the type implementation from users

  24. using a structure for ARITH Write a function fact : int -> integer such that for n ≥ 0, fact n = an integer value representing n! Available for use: rep : int -> integer add : integer * integer -> integer mult : integer * integer -> integer display : integer -> string

  25. factorial implementation - fun fact(n:int):integer = if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer - fact 3; val it = 6 : integer this should work for ANY structure that implements ARITH

  26. using Ints type integer = int val rep : int -> integer val display : integer -> string open Ints val add : integer * integer -> integer val mult : integer * integer -> integer fun fact(n:int):integer = if n=0 then rep 1 else mult(rep n, fact(n-1)) : integer : integer fact 3 =>* 6 : int fact 100 raises Overflow

  27. qualified names The built-in ML structures Int and String both define compare Use the structure name to disambiguate • Int.compare : int * int -> order • String.compare : string * string -> order

  28. using qualified names type Ints.integer = int fun fact(n:int): Ints.integer = if n=0 then Ints.rep 1 else Ints.mult(Ints.rep n, fact(n-1)) fact 3 =>* 6 : int fact 100 raises Overflow

  29. transparency We ascribed the signature transparently structure Ints : ARITH = ... open Ints; this is allowed , but a mistake fun fact(n:int):integer = if n=0 then ~1 else mult(rep n, fact(n-1)); : int : integer val fact = fn - : int -> integer transparency allows users to exploit the fact that integer is int and execute dangerous code

  30. transparency in action - fun fact(n:int):integer = if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer GOOD! - fact 3; val it = 6 : integer - fun fact(n:int):integer = if n=0 then ~1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer BAD!!!! - fact 3; val it = ~6 : integer

  31. opacity We can ascribe the signature opaquely using :> structure Ints :> ARITH = ... open Ints; fun fact(n:int):integer = if n=0 then ~1 else mult(rep n, fact(n-1)) : int : integer Error: types of if branches do not agree opacity prevents users from exploiting the fact that integer is int

  32. opacity in action - fun fact(n:int):integer = if n=0 then rep 1 else mult(rep n, fact(n-1)); val fact = fn : int -> integer - fact 3; GOOD! val it = 6 : integer - fun fact(n:int):integer = if n=0 then ~1 else mult(rep n, fact(n-1)); Error: types of if branches do not agree GOOD!

  33. ascription requirements Ints :> ARITH • For every type named in ARITH, Ints must have a type definition • For every value named in ARITH, Ints must have a suitable declaration same name , at least as general type (same as with transparent ascription)

Recommend


More recommend