Lecture 8 OOP v.s . FP, Subtyping

1 / 100

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

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

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

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

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 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
• OOP versus Functional decomposition
• Multi inheritance and Mixins
• Subtyping
Where are we?
• OOP versus Functional decomposition
• extensibility
• binary methods with both paradigm
• multimethods
• Multi inheritance and Mixins
• Subtyping
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 ...
• 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

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
• 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
• 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
• 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
• 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
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
• Functions allow new operations and objects allow new variants without modifying existing code even if they didn’t plan for it
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 we?
• OOP versus Functional decomposition
• extensibility
• binary methods with both paradigm
• multimethods
• Multi inheritance and Mixins
• Subtyping
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

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

Natural approach: pattern-match on the pair of values

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

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

Starts promising:

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

defeval

end

end

Classes Int, MyString, MyRationalthen all implement

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

class Int

… # what goes here?

end

end

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

class Int

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…
• 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”

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

[Definitely see the code]

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 we?
• OOP versus Functional decomposition
• extensibility
• binary methods with both paradigm
• multimethods
• Multi inheritance and Mixins
• Subtyping
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

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?

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?
• Yes, Java/C#/C++ allow multiple methods with the same name
• No, these language do not have multimethods
• 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 we?
• OOP versus Functional decomposition
• Multi-inheritance and Mixins
• multi-inheritance
• mixins
• interfaces
• abstract methods
• Subtyping
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 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
• 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
• 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?

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

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

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 we?
• OOP versus Functional decomposition
• Multi-inheritance and Mixins
• multi-inheritance
• mixins
• interfaces
• abstract methods
• Subtyping
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
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

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 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?
• 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 we?
• OOP versus Functional decomposition
• Multi-inheritance and Mixins
• multi-inheritance
• mixins
• interfaces
• abstract methods
• Subtyping
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
• 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

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
• 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
• 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
Where are we?
• OOP versus Functional decomposition
• Multi-inheritance and Mixins
• multi-inheritance
• mixins
• interfaces
• abstract methods
• Subtyping
Connections

• 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

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
• 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
• 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
• 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
• 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
• 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++
• 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 we?
• OOP versus Functional decomposition
• Multi-inheritance and Mixins
• Subtyping
• Records: a tiny language
• functions subtyping
• OOP subtyping
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
• 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
• 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)

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

{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

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

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

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

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

(S-RcdWidth)

(S-Trans)

(S-Refl)

(S-RcdPerm)

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

(S-RcdDepth)

Stop!

It breaks soundness 

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

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
• 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?
• 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
• 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 we?
• OOP versus Functional decomposition
• Multi-inheritance and Mixins
• Subtyping
• Records: a tiny language
• functions subtyping
• OOP subtyping
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?

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

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

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!

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

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)
• 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

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 we?
• OOP versus Functional decomposition
• Multi-inheritance and Mixins
• Subtyping
• Records: a tiny language
• functions subtyping
• OOP subtyping
An object is…
• Objects: mostly records holding fields and methods
• Fields are mutable
• 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
• 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#…

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
• 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?
• 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

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
• 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
• Using subtyping for containers is much more painful for clients

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
• 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?

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

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
• 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
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)

return result;

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?

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
• 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
• 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)