lecture 8 oop v s fp subtyping n.
Download
Skip this Video
Loading SlideShow in 5 Seconds..
Lecture 8 OOP v.s . FP, Subtyping PowerPoint Presentation
Download Presentation
Lecture 8 OOP v.s . FP, Subtyping

Loading in 2 Seconds...

play fullscreen
1 / 100

Lecture 8 OOP v.s . FP, Subtyping - PowerPoint PPT Presentation


  • 138 Views
  • Uploaded on

Lecture 8 OOP v.s . FP, Subtyping. After this lecture, you will: be able to argue the pros/cons of OOP and FP know Mixins – the mechanisim Ruby used for multi-inheritance master the subtyping rules. Roadmap. OOP versus Functional decomposition Multi inheritance and Mixins

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about 'Lecture 8 OOP v.s . FP, Subtyping' - leora


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
lecture 8 oop v s fp subtyping
Lecture 8OOP v.s. FP, Subtyping

After this lecture, you will:

  • be able to argue the pros/cons of OOP and FP
  • know Mixins – the mechanisim Ruby used for multi-inheritance
  • master the subtyping rules
roadmap
Roadmap
  • OOP versus Functional decomposition
  • Multi inheritance and Mixins
  • Subtyping
where are we
Where are we?
  • OOP versus Functional decomposition
      • extensibility
      • binary methods with both paradigm
      • multimethods
  • Multi inheritance and Mixins
  • Subtyping
breaking things down
Breaking things down
  • In functional (and procedural) programming, break programs down into functions that perform some operation
  • In object-oriented programming, break programs down into classes that give behavior to some kind of data
we will see
We will see ...
  • These two forms of decomposition are so exactly opposite that they are two ways of looking at the same “matrix”
  • Which form is “better” is somewhat personal taste, but also depends on how you expect to change/extend software
  • For some operations over two (multiple) arguments, functions and pattern-matching are straightforward, but with OOP we can do it with double dispatch(multiple dispatch)
the expression example
The expression example

Well-known and compelling example of a common pattern:

  • Expressions for a small language
  • Different variants of expressions: ints, additions, negations, …
  • Different operations to perform: eval, toString, hasZero, …
standard approach in ml
Standard approach in ML
  • Define a datatype, with one constructor for each variant
    • (No need to indicate datatypes if dynamically typed)
  • “Fill out the grid” via one function per column
    • Each function has one branch for each column entry
    • Can combine cases (e.g., with wildcard patterns) if multiple entries in column are the same
standard approach in oop
Standard approach in OOP
  • Define a class, with one abstractmethod for each operation
    • (No need to indicate abstract methods if dynamically typed)
  • Define a subclass for each variant
  • So “fill out the grid” via one class per row with one method implementation for each grid position
    • Can use a method in the superclass if there is a default for multiple entries in a column
a big course punchline
A big course punchline
  • FP and OOP often doing the same thing in exact opposite way
    • Organize the program “by rows” or “by columns”
  • Which is “most natural” may depend on what you are doing or personal taste
  • Code layout is important, but there is no perfect way since software has many dimensions of structure
extensibility
Extensibility
  • Functions [see ML code]:
    • Easy to add a new operation, e.g., noNegConstants
    • Adding a new variant, e.g., Mult requires modifying old functions, but ML type-checker gives a to-do list if original code avoided wildcard patterns
extensibility1
Extensibility
  • Objects [see Ruby code]:
    • Easy to add a new variant, e.g., Mult
    • Adding a new operation, e.g., noNegConstants requires modifying old classes
the other way is possible
The other way is possible
  • Functions allow new operations and objects allow new variants without modifying existing code even if they didn’t plan for it
thoughts on extensibility
Thoughts on Extensibility
  • Making software extensible is valuable and hard
    • If you know you want new operations, use FP
    • If you know you want new variants, use OOP
    • If both? Languages like Scalatry; it’s a hard problem
    • Reality: The future is often hard to predict!
where are we1
Where are we?
  • OOP versus Functional decomposition
      • extensibility
      • binary methods with both paradigm
      • multimethods
  • Multi inheritance and Mixins
  • Subtyping
binary operations
Binary operations
  • Situation is more complicated if an operation is defined over multiple arguments that can have different variants
    • Can arise in original program or after extension
  • Function decomposition deals with this much more simply…
example
Example

To show the issue:

  • Include variants String and Rational
  • (Re)define Add to work on any pair of Int, String, Rational
    • Concatenation if either argument a String, else math

Now just defining the addition operation is a different 2D grid:

ml approach
ML Approach

Natural approach: pattern-match on the pair of values

  • For commutative possibilities, can re-call with (v2,v1)

fun add_values(v1,v2)=

case (v1,v2)of

(Inti,Intj) => Int (i+j)

| (Inti, String s) => String (Int.toString i^s)

| (Inti, Rational(j,k)) => Rational (i*k+j,k)

| (Rational _, Int _) => add_values (v2,v1)

| … (* 5 more cases (3*3 total):see the code*)

fun eval e =

case e of

| Add(e1,e2) => add_values (eval e1, eval e2)

what about oop
What about OOP?

Starts promising:

  • Use OOP to call method add_valuesto one value with other value as result

class Add

defeval

e1.eval.add_values e2.eval

end

end

Classes Int, MyString, MyRationalthen all implement

  • Each handling 3 of the 9 cases: “add self to argument”

class Int

defadd_values v

… # what goes here?

end

end

first try
First try
  • This approach is common, but is “not as OOP”
    • So do not do it on your homework

class Int

defadd_valuesv

if v.is_a? Int

Int.new(v.i + i)

elsifv.is_a? MyRational

MyRational.new(v.i+v.j*i,v.j)

else

MyString.new(v.s + i.to_s)

end

end

another way
Another way…
  • add_valuesmethod in Intneeds “what kind of thing” v has
    • Same problem in MyRationaland MyString
  • In OOP, “always” solve this by calling a method on vinstead!
  • But now we need to “tell” v “what kind of thing” self is
    • We know that!
    • “Tell” v by calling different methods on v, passing self
  • Use a “programming trick” (?) called double-dispatch…
double dispatch trick
Double-dispatch “trick”
  • Int, MyString, and MyRational each define all of addInt, addString, and addRational
  • Add’seval method calls e1.eval.add_values e2.eval, which dispatches to add_values in Int, String, or Rational
    • Int’sadd_values: v.addInt self
    • MyString’sadd_values: v.addString self
    • MyRational’sadd_values: v.addRational self

So add_values performs “2nd dispatch” to the correct case of 9!

[Definitely see the code]

why showing you this
Why showing you this
  • To understand dynamic dispatch via a sophisticated idiom
  • Because required for the homework
  • To contrast with multimethods

Optional note: Double-dispatch also works fine with static typing

    • See Java code
    • Method declarations with types may help clarify
where are we2
Where are we?
  • OOP versus Functional decomposition
      • extensibility
      • binary methods with both paradigm
      • multimethods
  • Multi inheritance and Mixins
  • Subtyping
what would work better
What would work better?
  • Int, MyString, and MyRationaleach define three methods all named add_values
    • One add_valuestakes an Int, one a MyString, one a MyRational
    • So 9 total methods named add_values
    • e1.eval.add_values e2.eval picks the right one of the 9 at run-time using the classes of the two arguments
  • Such a semantics is called multimethods or multiple dispatch
multimethods
Multimethods

General idea:

  • Allow multiple methods with same name
  • Indicate which ones take instances of which classes
  • Use dynamic dispatch on arguments in addition to receiver to pick which method is called

If dynamic dispatch is essence of OOP, this is more OOP

  • No need for awkward manual multiple-dispatch
ruby why not
Ruby: Why not?

Multimethods a bad fit (?) for Ruby because:

  • Ruby places no restrictions on what is passed to a method
  • Ruby never allows methods with the same name
    • Same name means overriding/replacing
java c c why not
Java/C#/C++: Why not?
  • Yes, Java/C#/C++ allow multiple methods with the same name
  • No, these language do not have multimethods
    • They have static overloading
    • Uses static types of arguments to choose the method
      • But of course run-time class of receiver
  • Many languages have multimethods (e.g., C# 4.0, Clojure)
where are we3
Where are we?
  • OOP versus Functional decomposition
  • Multi-inheritance and Mixins
    • multi-inheritance
    • mixins
    • interfaces
    • abstract methods
  • Subtyping
what next
What next?
  • Multiple inheritance: allow > 1 superclasses
    • Useful but has some problems (see C++)
  • Ruby-style mixins: 1 superclass; > 1 method providers
    • Often a fine substitute for multiple inheritance and has fewer problems
  • Java/C#-style interfaces: allow > 1 types
    • Mostly irrelevant in a dynamically typed language, but fewer problems
multiple i nheritance
Multiple Inheritance
  • Is it useful? Sure!
    • Example: Make a ColorPt3D by inheriting from Pt3D and ColorPt (or maybe just from Color)
    • Example: Make a StudentAthlete by inheriting from Student and Athlete
    • With single inheritance, end up copying code or using non-OOP-style helper methods
multiple inheritance
Multiple Inheritance
  • If inheritance and overriding are so useful, why limit ourselves to one superclass?
    • Because the semantics is often awkward
    • Because it makes static type-checking harder
    • Because it makes efficient implementation harder
trees dags and diamonds
Trees, dags, and diamonds
  • Single inheritance: the class hierarchy is a tree
    • Nodes are classes
    • Parent is immediate superclass
    • Any number of children allowed
  • Multiple inheritance: the class hierarchy no longer a tree
    • Cycles still disallowed (a directed-acyclic graph)

A

B

C

D

E

X

V

W

Z

Y

what could go wrong
What could go wrong?

X

  • If V and Z both define a method m,

what does Y inherit? What does super mean?

  • What if X defines a method m that Z but not V overrides?
  • If X defines fields, should Y have one copy of them (f) or two (V::f and Z::f)?

W

V

Z

Y

3dcolorpoints
3DColorPoints

If Ruby had multiple inheritance, we would want ColorPt3D to inherit methods that share one @x and one @y

class Pt

attr_accessor:x, :y

end

class ColorPt< Pt

attr_accessor:color

end

class Pt3D < Pt

attr_accessor:z

… # override some methods

end

class ColorPt3D < Pt3D, ColorPt# not Ruby!

end

artistcowboys
ArtistCowboys

This code has Person define a pocket for subclasses to use, but an ArtistCowboy wants two pockets, one for each draw method

class Person

attr_accessor:pocket

end

class Artist < Person # pocket for brush objects

defdraw # access pocket

end

class Cowboy < Person # pocket for gun objects

defdraw # access pocket

end

class ArtistCowboy< Artist, Cowboy # not Ruby!

end

where are we4
Where are we?
  • OOP versus Functional decomposition
  • Multi-inheritance and Mixins
    • multi-inheritance
    • mixins
    • interfaces
    • abstract methods
  • Subtyping
mixins
Mixins
  • A mixin is (just) a collection of methods
    • Less than a class: no instances of it
  • Languages with mixins (e.g., Ruby modules) typically let a class have one superclass but include number of mixins
  • Semantics: Including a mixin makes its methods part of the class
    • Extending or overriding in the order mixins are included in the class definition
    • More powerful than helper methods because mixin methods can access methods (and instance variables) on self not defined in the mixin
example1
Example

module Doubler

defdouble

self + self # assume included in classes w/ +

end

end

class String

include Doubler

end

class AnotherPt

attr_accessor:x, :y

include Doubler

def+ other

ans = AnotherPt.new

ans.x = self.x + other.x

ans.y= self.y+ other.y

ans

end

lookup rules
Lookup rules

Mixins change our lookup rules slightly:

  • When looking for receiver obj's method m, look in obj's class, then mixins that class includes (later includes shadow), then obj's superclass, then the superclass' mixins, etc.
  • As for instance variables, the mixin methods are included in the same object
    • So usually bad style for mixin methods to use instance variables since a name clash would be like our CowboyArtist pocket problem (but sometimes unavoidable?)
the two big ones
The two big ones

The two most popular/useful mixins in Ruby:

  • Comparable: Defines <, >, ==, !=, >=, <= in terms of <=>
  • Enumerable: Defines many iterators (e.g., map, find) in terms of each

Great examples of using mixins:

    • Classes including them get a bunch of methods for just a little work
    • Classes do not “spend” their “one superclass” for this
    • Do not need the complexity of multiple inheritance
  • See the code for some examples
replacement for multiple inheritance
Replacement for multiple inheritance?
  • A mixinworks pretty well for ColorPt3D:
    • Color a reasonable mixin except for using an instance variable
  • A mixin works awkwardly-at-best for ArtistCowboy:
    • Natural for Artist and Cowboy to be Person subclasses
    • Could move methods of one to a mixin, but it is odd style and still does not get you two pockets

module Color

attr_accessor:color

end

module ArtistM …

class Artist <Person

include ArtistM

class ArtistCowboy<Cowboy

include ArtistM

where are we5
Where are we?
  • OOP versus Functional decomposition
  • Multi-inheritance and Mixins
    • multi-inheritance
    • mixins
    • interfaces
    • abstract methods
  • Subtyping
statically typed oop
Statically-Typed OOP
  • Now contrast multiple inheritance and mixins with Java/C#-style interfaces
  • Important distinction, but interfaces are about static typing, which Ruby does not have
  • So will use Java [pseudo]code after quick introduction to static typing for class-based OOP…
    • Sound typing for OOP prevents “method missing” errors
classes as types
Classes as Types
  • In Java/C#/etc. each class is also a type
  • Methods have types for arguments and result
  • If C is a (transitive) subclass of D, then C is a subtype of D
    • Type-checking allows subtype anywhere supertype allowed
    • So can pass instance of C to a method expecting instance of D

class A {

Object m1(Example e, String s) {…}

Integer m2(A foo, Boolean b, Integer i) {…}

}

interfaces are types
Interfaces are Types

interface Example {

void m1(intx, inty);

Object m2(Example x, String y);

}

  • An interface is not a class; it is only a type
    • Does not contain method definitions, only their signatures (types)
      • Unlike mixins
    • Cannot use new on an interface
      • Like mixins
implementing interfaces
Implementing Interfaces
  • A class can explicitly implement any number of interfaces
    • For class to type-check, it must implement every method in the interface with the right type
      • More on allowing subtypes later!
    • Multiple interfaces no problem; just implement everything
  • If class type-checks, it is a subtype of the interface

class A implements Example {

public void m1(intx, inty) {…}

public Object m2(Example e, String s) {…}

}

class B implements Example {

public void m1(intpizza, intbeer) {…}

public Object m2(Example e, String s) {…}

}

multiple interfaces
Multiple interfaces
  • Interfaces provide no methods or fields
    • So no questions of method/field duplication when implementing multiple interfaces, unlike multiple inheritance
  • What interfaces are for:
    • “Caller can give any instance of any class implementing I”
      • So callee can call methods in I regardless of class
    • So much more flexible type system
  • Interfaces have little use in a dynamically typed language
    • Dynamic typing already much more flexible, with trade-offs we studied
where are we6
Where are we?
  • OOP versus Functional decomposition
  • Multi-inheritance and Mixins
    • multi-inheritance
    • mixins
    • interfaces
    • abstract methods
  • Subtyping
connections
Connections

Answered in this segment:

  • What does a statically typed OOP language need to support “required overriding”?
  • How is this similar to higher-order functions?
  • Why does a language with multiple inheritance (e.g., C++) not need Java/C#-style interfaces?

[Explaining Java’s abstract methods / C++’s pure virtual methods]

required overriding
Required overriding

Often a class expects all subclasses to override some method(s)

  • The purpose of the superclass is to abstract common functionality, but some non-common parts have no default

A Ruby approach:

  • Do not define must-override methods in superclass
  • Subclasses can add it
  • Creating instance of superclass can cause method-missing errors

# do not use A.new

# all subclasses should define m2

class A

defm1 v

… self.m2 e …

end

end

static typing
Static typing
  • In Java/C#/C++, prior approach fails type-checking
    • No method m2 defined in superclass
    • One solution: provide error-causing implementation
    • Better: Use static checking to prevent this error…

class A

defm1 v

… self.m2 e …

end

defm2 v

raise "must be overridden"

end

end

abstract methods
Abstract methods
  • Java/C#/C++ let superclass give signature (type) of method subclasses should provide
    • Called abstract methods or pure virtual methods
    • Cannot creates instances of classes with such methods
      • Catches error at compile-time
      • Indicates intent to code-reader
      • Does not make language more powerful

abstract class A {

T1 m1(T2 x) { … m2(e); … }

abstract T3 m2(T4 x);

}

class B extends A {

T3 m2(T4 x) { … }

}

passing code to other code
Passing code to other code
  • Abstract methods and dynamic dispatch: An OOP way to have subclass “pass code” to other code in super class
  • Higher-order functions: An FP way to have caller “pass code” to callee

abstract class A {

T1 m1(T2 x) { … m2(e); … }

abstract T3 m2(T4 x);

}

class B extends A {

T3 m2(T4 x) { … }

}

fun f (g,x)= … g e …

fun h x = … f((fn y => …),…)

no interfaces in c
No interfaces in C++
  • If you have multiple inheritance and abstract methods, you do not also need interfaces
  • Replace each interface with a class with all abstract methods
  • Replace each “implements interface” with another superclass

So: Expect to see interfaces only in statically typed OOP without multiple inheritance

    • Not Ruby
    • Not C++
where are we7
Where are we?
  • OOP versus Functional decomposition
  • Multi-inheritance and Mixins
  • Subtyping
    • Records: a tiny language
    • functions subtyping
    • OOP subtyping
last major topic
Last major topic

Build up key ideas from first principles

    • In pseudocode because:
      • No time for another language
      • Simple to first show subtyping without objects

Then, a few segments from now:

  • How does subtyping relate to types for OOP?
    • Brief sketch only
  • What are the relative strengths of subtyping and generics?
  • How can subtyping and generics combine synergistically?
subtyping history
Subtyping history
  • Simula (Dahl and Nygaard, 1962-7)
    • First language with subtyping and inheritance
  • CLU (Liskov et. al., 1970s)
    • First language with good support for data abstraction (but no subtyping or inheritance)
  • Smalltalk (Kay et. al., 1970s)
    • First successful language and programming system to support subtyping and inheritance
a tiny language
A tiny language
  • Can cover most core subtyping ideas by just considering

records with mutable fields

  • Will make up our own syntax
    • ML has records, but no subtyping or field-mutation
    • Racket and Ruby have no type system
    • Java uses class/interface names and rarely fits on a slide
records half like ml half like java
Records (half like ML, half like Java)

Record creation (field names and contents):

Evaluate ei, make a record

Record field access:

Evaluate e to record v with an f field,

get contents of f field

Record field update

Evaluate e1 to a record v1 and

e2 to a value v2; Change v1's f field (which must exist) to v2;

Return v2

{f1=e1, f2=e2, …, fn=en}

e.f

e1.f = e2

a basic type system
A Basic Type System

{f1:t1, f2:t2, …, fn:tn}

Record types: What fields a record has and type for each field

Type-checking expressions:

  • If e1 has type t1, …, en has type tn,

then {f1=e1, …, fn=en} has type {f1:t1, …, fn:tn}

  • If e has a record type containing f:t,

then e.f has type t

  • If e1 has a record type containing f:t and e2 has type t,

then e1.f = e2 has type t

this is safe
This is safe

These evaluation rules and typing rules prevent ever trying to access a field of a record that does not exist

Example program that type-checks (in a made-up language):

fun distToOrigin(p:{x:real,y:real})=

Math.sqrt(p.x*p.x + p.y*p.y)

valpythag: {x:real,y:real}= {x=3.0, y=4.0}

valfive : real= distToOrigin(pythag)

motivating subtyping
Motivating subtyping

But according to our typing rules, this program does not type-check

  • It does nothing wrong and seems worth supporting

fun distToOrigin(p:{x:real,y:real})=

Math.sqrt(p.x*p.x + p.y*p.y)

valc : {x:real,y:real,color:string}=

{x=3.0, y=4.0, color="green"}

valfive : real= distToOrigin(c)

a good idea allow extra fields
A good idea: allow extra fields

Natural idea: If an expression has type

{f1:t1, f2:t2, …, fn:tn}

Then it can also have a type with some fields removed

This is what we need to type-check these function calls:

fun distToOrigin(p:{x:real,y:real})= …

fun makePurple(p:{color:string})= …

valc :{x:real,y:real,color:string}=

{x=3.0, y=4.0, color="green"}

val_ =distToOrigin(c)

val _ = makePurple(c)

keeping subtyping separate
Keeping subtyping separate

We can do this by adding “just two things to our language”

  • Subtyping: Write t1 <: t2 for t1 is a subtype of t2
  • One new typing rule that uses subtyping:

If e has type t1 and t1 <: t2,

then e (also) has type t2

Now all we need to do is define t1 <: t2

four good rules
Four good rules

(S-RcdWidth)

(S-Trans)

(S-Refl)

(S-RcdPerm)

more record subtyping
More record subtyping?

[Warning: I am misleading you ]

For this to type-check, we need:

{center:{x:real,y:real,z:real}, r:real}

<:

{center:{x:real,y:real}, r:real}

fun circleY(c:{center:{x:real,y:real}, r:real})=

c.center.y

valsphere:{center:{x:real,y:real,z:real},r:real})= {center={x=3.0,y=4.0,z=0.0}, r=1.0}

val_ =circleY(sphere)

depth subtyping
Depth subtyping

(S-RcdDepth)

Stop!

It breaks soundness 

mutation strikes again
Mutation strikes again

If ta <: tb,

then {f1:t1, …, f:ta, …, fn:tn} <:

{f1:t1, …, f:tb, …, fn:tn}

fun setToOrigin(c:{center:{x:real,y:real},r:real})=

c.center= {x=0.0, y=0.0}

valsphere:{center:{x:real,y:real,z:real}, r:real})= {center={x=3.0, y=4.0, z=0.0}, r=1.0}

val_ =setToOrigin(sphere)

val_ =sphere.center.z(* kaboom! (no z field) *)

reference subtyping
Reference subtyping

The Ref type constructor must be invariant to preserve safety.

(S-Ref)

Why such a severe restriction? Because a Ref type value can be read and written!

arrays and subtyping
Arrays and subtyping
  • Array is a collection of references of the same type.
  • However, Java permits covariant subtyping of arrays

(S-Array)

(S-ArrayJava)

  • This is unsafe and requires every array assignment to have a run-time type check!
why java did this
Why Java did this?
  • To receive a more flexible type system
  • Very high cost. Better solution?

void reverse(Object[] a){

...

a[i] = b[j]

...}

Int[] a = new Int[100];

...

reverse(a);

moral of the story
Moral of the story
  • In a language with records/objects with getters and setters, depth subtyping is unsound
    • Subtyping cannot change the type of fields
  • If fields are immutable, then depth subtyping is sound!
    • Yet another benefit of outlawing mutation!
    • Choose two of three: setters, depth subtyping, soundness
where are we8
Where are we?
  • OOP versus Functional decomposition
  • Multi-inheritance and Mixins
  • Subtyping
    • Records: a tiny language
    • functions subtyping
    • OOP subtyping
now functions
Now functions
  • Important for higher-order functions: If a function expects an argument of type t1 -> t2, can you pass a t3 -> t4 instead?
  • What’s the subtyping rule looks like?t3->t4 <: t1 -> t2

provided t3<:t1? or t4<:t2?

example2
Example

fun distMoved(f : {x:real,y:real}->{x:real,y:real},

p : {x:real,y:real}) =

let valp2 : {x:real,y:real} = f p

valdx : real = p2.x – p.x

valdy: real = p2.y – p.y

in Math.sqrt(dx*dx + dy*dy) end

fun flip p ={x = ~p.x, y=~p.y}

vald = distMoved(flip, {x=3.0, y=4.0})

return type subtyping
Return-type subtyping

fun distMoved(f : {x:real,y:real}->{x:real,y:real},

p : {x:real,y:real}) =

let valp2 : {x:real,y:real} = f p

valdx : real = p2.x – p.x

valdy: real = p2.y – p.y

in Math.sqrt(dx*dx + dy*dy) end

fun flipGreen p ={x = ~p.x, y=~p.y, color="green"}

vald = distMoved(flipGreen, {x=3.0, y=4.0})

  • Ifta<:tb, thent->ta <: t->tb
this is wrong
This is wrong

fun distMoved(f : {x:real,y:real}->{x:real,y:real},

p : {x:real,y:real}) =

let valp2 : {x:real,y:real} = f p

valdx : real = p2.x – p.x

valdy: real = p2.y – p.y

in Math.sqrt(dx*dx + dy*dy) end

fun flipIfGreen p =if p.color = "green" (*kaboom!*)

then {x = ~p.x, y=~p.y}

else{x = p.x, y=p.y}

vald = distMoved(flipIfGreen, {x=3.0, y=4.0})

  • Unsound! ta<:tbdoes NOT allow ta->t <:tb->t
the other way works
The other way works!

fun distMoved(f : {x:real,y:real}->{x:real,y:real},

p : {x:real,y:real}) =

let valp2 : {x:real,y:real} = f p

valdx : real = p2.x – p.x

valdy: real = p2.y – p.y

in Math.sqrt(dx*dx + dy*dy) end

fun flipX_Y0 p ={x = ~p.x, y=0.0}

vald = distMoved(flipX_Y0, {x=3.0, y=4.0})

  • If tb<:ta, then ta->t<:tb->t
can do both
Can do both

fun distMoved(f : {x:real,y:real}->{x:real,y:real},

p : {x:real,y:real}) =

let valp2 : {x:real,y:real} = f p

valdx : real = p2.x – p.x

valdy: real = p2.y – p.y

in Math.sqrt(dx*dx + dy*dy) end

fun flipXMakeGreen p = {x = ~p.x, y=0.0, color="green"}

vald = distMoved(flipXMakeGreen, {x=3.0, y=4.0})

  • If t3<:t1 and t2<:t4, then t1->t2<:t3->t4
conclusion till now
Conclusion (till now)
  • If t3 <: t1 and t2 <: t4, then t1 -> t2 <: t3 -> t4
    • Function subtyping contravariant in argument(s) and covariant in results
  • The most unintuitive concept in this course
    • Smart people often forget and convince themselves that covariant arguments are okay
    • These smart people are always mistaken
a quiz
A quiz

f = x:floatfloat. sqrt(x(2.3)) f : (floatfloat)float

Assume we have float <: int, which can be taken as f(gi)?

g1: int -> int

g2: float -> float

g3: int -> float

g4: float -> int

where are we9
Where are we?
  • OOP versus Functional decomposition
  • Multi-inheritance and Mixins
  • Subtyping
    • Records: a tiny language
    • functions subtyping
    • OOP subtyping
an object is
An object is…
  • Objects: mostly records holding fields and methods
    • Fields are mutable
    • Methods are immutable functions that also have access to self
  • So could design a type system using types very much like record types
    • Subtypes could have extra fields and methods
    • Overriding methods could have contravariant arguments and covariant results compared to method overridden
      • Sound only because method “slots” are immutable!
objects and records
Objects and records
  • An object can be viewed as a record with object fields and methods.
  • So could design a type system using types very much like record types
    • Subtypes could have extra fields and methods
    • Overriding methods could have subtypes (methods are immutable)
actual java c
Actual Java/C#…

Compare/contrast to what our “theory” allows:

1. Types are class names and subtyping are explicit subclasses

2. A subclass can add fields and methods

3. A subclass can override a method with a covariant return type

classes vs types
Classes vs. Types
  • A class defines an object's behavior
    • Subclassing inherits behavior and changes it via extension and overriding
  • A type describes an object's methods’ argument/result types
    • A subtype is substitutable in terms of its field/method types
  • These are separate concepts: try to use the terms correctly
review why java did this
Review: Why Java did this?
  • To receive a more flexible type system
  • Very high cost. Better solution?

void reverse(Object[] a){

...

a[i] = b[j]

...}

Int[] a = new Int[100];

...

reverse(a);

generics parametric polymorphism
Generics – parametric polymorphism

Some good uses for parametric polymorphism:

  • General point: When types can “be anything” but multiple things need to be “the same type”

fun compose (g,h) =fnx => g (h x)

(*compose:('b->'c) * ('a->'b) -> ('a->'c)*)

vallength : 'a list -> int

valmap : ('a -> 'b) -> 'a list -> 'b list

valswap : ('a *'b) -> ('b * 'a)

generics in java
Generics in Java
  • Java generics a bit clumsier syntactically and semantically, but can express the same ideas
    • Without closures, often need to use (one-method) objects

class Pair<T1,T2> {

T1 x;

T2 y;

Pair(T1 _x, T2 _y){ x = _x; y = _y; }

Pair<T2,T1> swap() {

returnnew Pair<T2,T1>(y,x);

}

}

subtyping is not good for this
Subtyping is not good for this
  • Using subtyping for containers is much more painful for clients
    • Have to downcast items retrieved from containers

class LamePair {

Object x;

Object y;

LamePair(Object _x,Object _y){x=_x;y=_y;}

LamePairswap() {returnnew LamePair(y,x);}

}

// error caught only at run-time:

String s = (String)(newLamePair("hi",4).y);

casts
Casts
  • Upcasts: if S <: T and e:S then writing (T)e is harmless and just has the effect of losing information about e .
  • Downcasts: if S <: T and e:T then writing (S)e requires a run-time check that e actually does have type S.
  • Downcasts have run-time cost.
what is subtyping good for
What is subtyping good for?

Some good uses for subtype polymorphism:

  • Code that “needs a Foo” but fine to have “more than a Foo”
  • Geometry on points works fine for colored points
awkward in ml
Awkward in ML

ML does not have subtyping, so this simply does not type-check:

Cumbersome workaround: have caller pass in getter functions:

(* {x:real, y:real} -> real *)

fun distToOrigin({x=x,y=y}) =

Math.sqrt(x*x + y*y)

valfive = distToOrigin {x=3.0,y=4.0,color="red"}

(* ('a -> real) * ('a -> real) * 'a -> real *)

fun distToOrigin(getx,gety, v) =

Math.sqrt((getx v)*(getx v)

+ (gety v)*(gety v))

wanting both
Wanting both
  • Could a language have generics and subtyping?
    • Sure!
  • More interestingly, want to combine them
    • “Any type T1 that is a subtype of T2”
    • This is bounded polymorphism
    • Lets you do things naturally you cannot do with generics or subtyping separately
example3
Example

Basic method signature:

Java implementation straightforward assuming Pointhas a distance method

List<Point> inCircle(List<Point> pts,

Point center,

double r) { … }

List<Point> result = new ArrayList<Point>();

for(Point pt: pts)

if(pt.distance(center) <= r)

result.add(pt);

return result;

subtyping
Subtyping?

List<Point> inCircle(List<Point> pts,

Point center,

double r) { … }

  • Would like to use inCircle by passing a List<ColorPoint>and getting back a List<ColorPoint>
  • Java rightly disallows this: While inCirclewould “do nothing wrong” its type does not prevent:
    • Returning a list that has a non-color-point in it
generics
Generics?

List<Point> inCircle(List<Point> pts,

Point center,

double r) { … }

  • We could change the method to be
    • Now the type system allows passing in a List<Point>to get a List<Point> returned or a List<ColorPoint> to get a List<ColorPoint> returned
    • But we cannot implement inCircleproperly because method body should have no knowledge of type T

List<T> inCircle(List<T> pts,

Point center,

double r) { … }

bounds
Bounds
  • What we want:
  • Caller uses it generically, but must instantiate T with a subtype of Point(including Point)
  • Callee can assume T <: Point so it can do its job
  • Callee must return a List<T>so output will contain only list elements from input

List<T> inCircle(List<T> pts,

Point center,

double r) where T <: Point

{ … }

real java
Real Java
  • The actual Java syntax
  • For backward-compatibility and implementation reasons, in Java there is actually always a way to use casts to get around the static checking with generics
    • With or without bounded polymorphism

<T extends Pt> List<T> inCircle(List<T> pts,

Ptcenter,

double r) {

List<T> result = new ArrayList<T>();

for(T pt: pts)

if(pt.distance(center) <= r)

result.add(pt);

return result;

}

conclusion
Conclusion
  • OOP versus Functional decomposition
  • Multi inheritance and Mixins
  • Subtyping