51
Programming Languages Section 4 1 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Embed Size (px)

Citation preview

Page 1: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 1

Programming Languages

Section 4. SML Remaining Topics

Xiaojuan Cai

Spring 2015

Page 2: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 2

Remaining Topics

• Type Inference

• Mutual Recursion

• Module System

• Equivalence – optional

• Lambda calculus – in separate slides

Page 3: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 3

Some concepts• Static type-checking v.s. Dynamic type-

checkingStatic: ML, Java, C, ..Dynamic: Racket, Ruby, Python, ...

• Implicit type v.s. Explicit typeImplicit: ML, Racket, ...Explicit: C, Java, ...

• Type-checking v.s. Type inferenceType-checking: check type consistencyType inference: infer types for implicit type language

Page 4: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 4

ML type inference• Determine types of bindings in order • (Except for mutual recursion)

• For each val or fun binding:• Analyze definition for all necessary facts

(constraints)• Example: If see x > 0, then x must have

type int• Type error if no way for all facts to hold

• Afterward, use type variables (e.g., 'a) for any unconstrained types• Example: An unused argument can have any

type

Page 5: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 5

Very simple example

val x = 42 (* val x : int *)

fun f (y, z, w) = if y (* y must be bool *) then z + x (* z must be int *) else 0 (* both branches have same type *)(* f must return an int f must take a bool * int * ANYTHING so val f : bool * int * 'a -> int *)

Page 6: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 6

Relation to Polymorphism

•Central feature of ML type inference: it can infer types with type variables

•But remember there are two orthogonal concepts•Languages can have type

inference without type variables•Languages can have type variables

without type inference

Page 7: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 7

More examples

•Collect all the facts needed for type-checking

•These facts constrain the type of the function

•See the code file and/or the reading notes

Page 8: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 8

A local optimum• Despite the value restriction, ML type inference

is elegant and fairly easy to understand

• More difficult without polymorphism

• What type should length-of-list have?

• More difficult with subtyping

• val (y,z) = x constrains x to have at least two fields, not exactly two fields

• Depending on details, languages can support this, but types often more difficult to infer and understand

• Will study subtyping later, but not with type inference

Page 9: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 9

Where are we?

•Type Inference

•Mutual Recursion

•Module System

•Equivalence

Page 10: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 10

Mutual Recursion

•Allow f to call g and g to call f

•Useful? Yes.

• Idiom we will show: implementing state machines

Page 11: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 11

New language features• Mutually recursive functions (the and

keyword)

• Similarly, mutually recursive datatype bindings

• Everything in “mutual recursion bundle” type-checked together and can refer to each other

fun f1 p1 = e1and f2 p2 = e2 and f3 p3 = e3

datatype t1 = …and t2 = … and t3 = …

Page 12: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 12

State-machine example

• Each “state of the computation” is a function

• “State transition” is “call another function” with “rest of input”

• Generalizes to any finite-state-machine example

fun state1 input_left = …

and state2 input_left = …

and …

Page 13: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 13

Work-around

• Suppose we did not have support for mutually recursive functions

• Can have the “later” function pass itself to the “earlier” one

• Yet another higher-order function idiom

fun earlier (f,x) = … f y …

… (* no need to be nearby *)

fun later x = … earlier(later,y) …

Page 14: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 14

Where are we?

•Type Inference

•Mutual Recursion

•Module System

•Equivalence

Page 15: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 15

Modules

Inside a moduleCan have any kind of binding (val, datatype, exception, ...)

Outside a module, Can refer to modules’ bindings via

ModuleName.bindingName

structure MyModule = struct bindings end

Page 16: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 16

Examplestructure MyMathLib = struct

fun fact x = if x=0 then 1 else x * fact(x-1)

val half_pi = Math.pi / 2

fun doubler x = x * 2

end

Page 17: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 17

Open

• Can use open ModuleName to get “direct” access to a module’s bindings

• Never necessary; just a convenience; often bad style

• Often better to create local val-bindings for just the bindings you use a lot, e.g., val map = List.map

Page 18: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 18

Namespace management

• So far, this is just namespace management

• Giving a hierarchy to names to avoid shadowing

• Allows different modules to reuse names, e.g., map

• Very important, but not very interesting

Page 19: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 19

Signatures

signature MATHLIB = sig val fact : int -> intval half_pi : real val doubler : int -> intend

structure MyMathLib :> MATHLIB = struct fun fact x = …val half_pi = Math.pi / 2.0fun doubler x = x * 2end

Page 20: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 20

In general

• Signatures

• Can include variables, types, datatypes, and exceptions defined in module

• Ascribing a signature to a module

• Module will not type-check unless it matches the signature, meaning it has all the bindings at the right types

structure MyModule :> SIGNAME = struct bindings end

signature SIGNAME = sig types-for-bindings end

Page 21: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 21

Hiding things

Real value of signatures is to to hide bindings and type definitions

Hiding implementation details is the most important strategy for writing correct, robust, reusable software

Page 22: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 23

Example

Outside the module, MyMathLib.doubler is simply unbound

signature MATHLIB = sig val fact : int -> intval half_pi : real end

structure MyMathLib :> MATHLIB = struct fun fact x = …val half_pi = Math.pi / 2.0fun doubler x = x * 2end

Page 23: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 24

A larger exampleNow consider a module that defines an Abstract Data Type (ADT)Our example: rational numbers supporting add and toString

structure Rational1 = struct datatype rational = Whole of int | Frac of int*intexception BadFrac

(*internal functions gcd and reduce not on slide*)

fun make_frac (x,y) = …fun add (r1,r2) = …fun toString r = …end

Page 24: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 25

Library spec and invariants

Properties [externally visible guarantees, up to library writer]

• Disallow denominators of 0

• Return strings in reduced form (“4” not “4/1”, “3/2” not “9/6”)

• No infinite loops or exceptions

Invariants [part of the implementation, not the module’s spec]

• All denominators are greater than 0

• All rational values returned from functions are reduced

Page 25: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 26

More on invariantsOur code maintains the invariants and relies on them

Maintain:

• make_frac disallows 0 denominator, removes negative denominator, and reduces result

• add assumes invariants on inputs, calls reduce if needed

Rely:

• gcd does not work with negative arguments, but no denominator can be negative

• add uses math properties to avoid calling reduce

• toString assumes its argument is already reduced

Page 26: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 27

A first signatureWith what we know so far, this signature makes sense:

•gcd and reduce not visible outside the module

signature RATIONAL_A = sig datatype rational = Whole of int | Frac of int*intexception BadFracval make_frac : int * int -> rationalval add : rational * rational -> rationalval toString : rational -> stringend

structure Rational1 :> RATIONAL_A = …

Page 27: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 28

The problem

Any of these would lead to exceptions, infinite loops, or wrong results, which is why the module’s code would never return them

• Rational1.Frac(1,0)

• Rational1.Frac(3,~2)

• Rational1.Frac(9,6)

signature RATIONAL_A = sig datatype rational = Whole of int | Frac of int*int…

Page 28: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 29

So hide more

Key idea: An ADT must hide the concrete type definition

Alas, this attempt doesn’t work because the signature now uses a type rational that is not known to exist:

signature RATIONAL_WRONG = sig exception BadFracval make_frac : int * int -> rationalval add : rational * rational -> rationalval toString : rational -> stringend

structure Rational1 :> RATIONAL_WRONG = …

Page 29: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 30

Abstract typesSo ML has a feature for exactly this situation:

In a signature:

type foo

means the type exists, but clients do not know its definition

signature RATIONAL_B = sig type rationalexception BadFracval make_frac : int * int -> rationalval add : rational * rational -> rationalval toString : rational -> stringend

structure Rational1 :> RATIONAL_B = …

Page 30: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 31

This works!

• Only way to make first rational is Rational1.make_frac

• After that can use only Rational1.make_frac, Rational1.add, and Rational1.toString

• Hides constructors and patterns – don’t even know whether or not Rational1.rational is a datatype

signature RATIONAL_B = sig type rationalexception BadFracval make_frac : int * int -> rationalval add : rational * rational -> rationalval toString : rational -> stringend

Page 31: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 32

Two key restrictions

So we have two powerful ways to use signatures for hiding:

1. Deny bindings exist (val-bindings, fun-bindings, constructors)

2. Make types abstract (so clients cannot create values of them or access their pieces directly)

Page 32: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 33

A cute twistIn our example, exposing the Whole constructor is no problem

• Still hiding the rest of the datatype

• Still does not allow using Whole as a pattern

signature RATIONAL_C = sig type rationalexception BadFracval Whole : int -> rationalval make_frac : int * int -> rationalval add : rational * rational -> rationalval toString : rational -> stringend

Page 33: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 34

Signature matching

structure Foo :> BAR is allowed if:

• Every non-abstract type in BAR is provided in Foo, as specified

• Every abstract type in BAR is provided in Foo in some way

• Can be a datatype or a type synonym

• Every val-binding in BAR is provided in Foo, possibly with a more general and/or less abstract internal type

• Every exception in BAR is provided in Foo

Page 34: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 35

Equivalent implementationsExample (see code file):

•structure Rational2 does not keep rationals in reduced form, instead reducing them “at last moment” in toString

• Also make gcd and reduce local functions

•Not equivalent under RATIONAL_A

• Rational1.toString(Rational1.Frac(9,6)) = "9/6"

• Rational2.toString(Rational2.Frac(9,6)) = "3/2”

•Equivalent under RATIONAL_B or RATIONAL_C

• Different invariants, but same properties

• Essential that type rational is abstract

Page 35: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 36

More interesting example

Given a signature with an abstract type, different structures can:

• Have that signature

• But implement the abstract type differentlySuch structures might or might not be equivalentExample (see code):

• type rational = int * int

• Does not have signature RATIONAL_A

• Equivalent to both previous examples under RATIONAL_B or RATIONAL_C

Page 36: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 37

More interesting example

structure Rational3 = struct type rational = int * intexception BadFrac

fun make_frac (x,y) = …fun Whole i = (i,1) (* needed for RATIONAL_C *) fun add ((a,b)(c,d)) = (a*d+b*c,b*d)fun toString r = … (* reduce at last minute *)end

Page 37: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 38

Some interesting things• Internally make_frac has type int * int ->

int * int, but externally int * int -> rational

• Could give type rational -> rational in signature, but this is awful: makes entire module unusable – why?

• Internally Whole has type 'a -> 'a * int but externally int -> rational

• This matches because we can specialize 'a to int and then abstract int * int to rational

Page 38: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 39

Can’t mix-and-match module bindings

Modules with the same signatures still define different typesSo things like this do not type-check:

• Rational1.toString(Rational2.make_frac(9,6))

• Rational3.toString(Rational2.make_frac(9,6))

Page 39: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 40

Where are we?

•Type Inference

•Mutual Recursion

•Module System

•Equivalence

Page 40: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 41

Equivalence

Must reason about “are these equivalent” all the time

•Code maintenance: Can I simplify this code?

•Backward compatibility: Can I add new features without changing how any old features work?

•Optimization: Can I make this code faster?

•Abstraction: Can an external client tell I made this change?

Page 41: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 42

A definition

Two functions are equivalent if they have the same “observable behavior” in any context: Given equivalent arguments, they:

• Produce equivalent results

• Have the same (non-)termination behavior

• Mutate (non-local) memory in the same way

• Do the same input/output

• Raise the same exceptions

Page 42: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 43

ExampleSince looking up variables in ML has no side effects, these two functions are equivalent:

But these next two are not equivalent in general: it depends on what is passed for f

• Are equivalent if argument for f has no side-effects

• Example: g ((fn i => print "hi" ; i), 7)

fun f x = x + xval y = 2fun f x = y * x

fun g (f,x) = (f x) + (f x)

val y = 2fun g (f,x) = y * (f x)

Page 43: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 44

Another example• Again: pure functions make more things

equivalent

• Example: g divides by 0 and h mutates a top-level reference

• Example: g writes to a reference that h reads from

fun f x = let val y = g x val z = h x in (y,z) end

fun f x = let val z = h x

val y = g x in (y,z) end

Page 44: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 45

Syntactic sugar

Using or not using syntactic sugar is always equivalent

• By definition, else not syntactic sugar

But be careful about evaluation order

fun f x = if x then g x else false

fun f x = x andalso g x

fun f x = if g x then x else false

fun f x = x andalso g x

Page 45: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 46

Standard equivalences1. Consistently rename bound variables and uses

But notice you can’t use a variable name already used in the function body to refer to something else

val y = 14fun f x = x+y+x

val y = 14fun f z = z+y+z

val y = 14fun f x = x+y+x

val y = 14fun f y = y+y+y

fun f x = let val y = 3 in x+y end

fun f y = let val y = 3 in y+y end

Page 46: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 47

Standard equivalences2. Use a helper function or do not

But notice you need to be careful about environments

val y = 14fun f x = x+y+xfun g z = (f z)+z

val y = 14fun g z = (z+y+z)+z

val y = 14fun f x = x+y+xval y = 7fun g z = (f z)+z

val y = 14val y = 7fun g z = (z+y+z)+z

Page 47: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 48

Standard equivalences

3. Unnecessary function wrapping

But notice that if you compute the function to call and that computation has side-effects, you have to be careful

fun f x = x+xfun g y = f y

fun f x = x+xval g = f

fun f x = x+xfun h () = (print "hi";

f)fun g y = (h()) y

fun f x = x+xfun h () = (print "hi";

f)val g = (h())

Page 48: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 49

One moreIf we ignore types, then ML let-bindings can be syntactic sugar for calling an anonymous function:

• These both evaluate e1 to v1, then evaluate e2 in an environment extended to map x to v1

• So exactly the same evaluation of expressions and result

But in ML, there is a type-system difference:

• x on the left can have a polymorphic type, but not on the right

• Can always go from right to left

• If x need not be polymorphic, can go from left to right

let val x = e1in e2 end

(fn x => e2) e1

Page 49: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 50

What about performance?

According to our definition of equivalence, these two functions are equivalent, but we learned one is awful

• (Actually we studied this before pattern-matching)fun max xs =

case xs of [] => raise Empty | x::[] => x | x::xs’ => if x > max xs’ then x else max xs’

fun max xs = case xs of [] => raise Empty | x::[] => x | x::xs’ => let val y = max xs’ in if x > y then x else y end

Page 50: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 51

Different definitions for different jobs• PL Equivalence: given same inputs, same

outputs and effects

• Good: Lets us replace bad max with good max

• Bad: Ignores performance in the extreme

• Asymptotic equivalence: Ignore constant factors

• Good: Focus on the algorithm and efficiency for large inputs

• Bad: Ignores “four times faster”

• Systems equivalence: Account for constant overheads, performance tune

• Good: Faster means different and better

• Bad: Beware overtuning on “wrong” (e.g., small) inputs; definition does not let you “swap in a different algorithm”

Page 51: Programming LanguagesSection 41 Programming Languages Section 4. SML Remaining Topics Xiaojuan Cai Spring 2015

Programming Languages Section 4 52

Conclusion

•Type Inference

•Mutual Recursion

•Module System

•Equivalence