CSE 130: Fall 2012

Subtyping

Ranjit Jhala, UC San Diego

News

Brief Digression Into PA6

Today's Plan: Subtyping

When is an object like another?

Example

Essential for safe code reuse

Recap: Objects and Types

Objects

Recap: Types

Description of object properties

Recap: Types

Example: ticker

{ def next(): Any
  def hasNext: Boolean }

Example: Int

{ def .+(other: Int) : Int // add       method
  def .-(other: Int) : Int // subtract  method
  ...//etc }

Recap: Types

Example: Point

class Point {
  val name : String                 // immutable field
  var x    : Int                    // mutable field
  var y    : Int                    // mutable field
  def render(g: Graphics): Unit     // method 
  def move(d: Dimension, del: Int)  // method
} 

Recall ScreenSaver

def screenSaver(p: Point) {

  // Only uses properties described in Point 

}

A Type is a Contract

What is a Point ?

What objects can we really call ScreenSaver with?

Subtyping

What objects can we really call ScreenSaver with?

Notation: S <: T means type S is a subtype of type T

If S<:T then we can use S object in place of T object

If S<:T then S object IS A T object

Subtype Polymorphism

Subtype Polymorphism

If S <: T then we can use S objects in place of T objects

def screen(p: Point) {
  // Only uses properties described in Point 
}

So screen can be called with Dot, ColorDot, Box, ColorBox

Sounds great in theory, but when is S <: T

Sounds great in theory, but when is S <: T

Intuition

When every object of type S "IS A" object of type T

Sounds great in theory, but when is S <: T

Example: S <: T if S has all attributes of T

class ColorDot {
  val color : Color  // Extra Field
  val name  : String
  var x     : Int
  var y     : Int
  def render(g: Graphics): Unit
  def move(d: Dimension, delta: Int) 
}

is a (subtype of)

class Dot {
  val name : String
  var x    : Int
  var y    : Int
  def render(g: Graphics): Unit
  def move(d: Dimension, delta: Int) 
}

Subtyping Rule S <: T

Width Subtyping: when S has more attributes than T

If

Then

Hold on a minute!

We know

But how do we get

Subtyping Rule S <: T

Transitive Subtyping: The Is-A relation is transitive

If

Then

But intuition can be a tricky thing...

Given types S and T, how can we know if S <: T ?

We know ColorDot <: Dot. What about ColorLine and Line?

class Line {
  val p1: Dot
  val p2: Dot
}

and

class ColorLine {
  val p1: ColorDot
  val p2: ColorDot
}

QUIZ: How can we know if S <: T ?

We know ColorDot <: Dot. What about ColorLine and Line?

class Line {
  val p1: Dot
  val p2: Dot
}

class ColorLine {
  val p1: ColorDot
  val p2: ColorDot
}

For any function def foo(l: Line), is it OK to call foo with a ColorLine ?

A. Hells, Yes!

B. OMG, NO!

Given types S and T, how can we know if S <: T ?

We know ColorDot <: Dot.

What about ColorLine and Line?

For any function

def foo(l: Line)

Is it OK to call foo with a ColorLine ? Yes

Subtyping Rule S <: T

Depth Subtyping: when S fields are subtypes of T fields

If

Then

Digression: Subtyping on Function Types ...

Given types S and T, how can we know if S <: T ?

Subtyping Function Types

Given types S and T, how can we know if S <: T ?

Subtyping Function Types

Output Subtyping: When is In=>S <: In=>T ?

Is () => ColorDot <: () => Dot ?

Suppose I have a method whose input has type () => Dot

def screenSaver(f:() => Dot): Unit

Also, suppose I have a function that returns ColorDot <: Dot

val getColorDot: () => ColorDot

QUIZ: Is it safe to do screenSaver(getColorDot) ?

A. Yes

B. No

Output Subtyping: When is In=>S <: In=>T ?

Is () => ColorDot <: () => Dot ?

Suppose I have a method whose input has type () => Dot

def screenSaver(f:() => Dot): Unit

Also, suppose I have a function that returns ColorDot <: Dot

val getColorDot: () => ColorDot

QUIZ: Is it safe to do screenSaver(getColorDot) ? YES

Subtyping Rule In => S <: In => T

Function Subtyping

Can substitute/use a function with another that produces fewer outputs ...

If

Then

Example

Given types S and T, how can we know if S <: T ?

Subtyping Function Types

Input Subtyping: When is S=>Out <: T=>Out ?

Is ColorDot=>Graphic <: Dot=>Graphic ?

If I have a function whose input must be Dot => Graphic

def plotGraphic(x: Dot => Graphic): Unit

and a function from ColorDot to Graphic

val cd2g: ColorDot => Graphic

QUIZ: Is it safe to do plotGraphic(cd2g) ?

A. Yes

B. No

Input Subtyping: When is S=>Out <: T=>Out ?

Is ColorDot=>Graphic <: Dot=>Graphic ?

def plotGraphic(f: Dot => Graphic) {
  val d = new Dot(0, 0) // not ColorDot
  plot(f(d))    
}

def cd2g(cd: ColorDot) = { 
  val c = cd.color
  setColor(cd.color)
  fillColorOval()
} 

What happens if we do ...

plotGraphic(cd2g)

?

Input Subtyping: When is S=>Out <: T=>Out ?

Is ColorDot=>Graphic <: Dot=>Graphic ?

def plotGraphic(f: Dot => Graphic) {
  val d = new Dot(0, 0) // not ColorDot
  plot(f(d))            // cd2g(d) crashes! 
}

def cd2g(cd: ColorDot) = { 
  val c = cd.color
  setColor(cd.color)
  fillColorOval()
} 

plotGraphic(cd2g)

Yikes! the call f(d) in plotGraphic crashes!

Input Subtyping: When is S=>Out <: T=>Out ?

Is ColorDot=>Graphic <: Dot=>Graphic ?

Input Subtyping: When is S=>Out <: T=>Out ?

How about Dot=>Graphic <: ColorDot=>Graphic ?

Input Subtyping: When is S=>Out <: T=>Out ?

How about Dot=>Graphic <: ColorDot=>Graphic ?

Suppose we have:

def plotColorGraphic(f: ColorDot => Graphic)

def d2g(d: Dot)

Would plotGraphic(cd2g) cause any problems?

Input Subtyping: When is S=>Out <: T=>Out ?

How about Dot=>Graphic <: ColorDot=>Graphic ?

Suppose we have:

def plotColorGraphic(f: ColorDot => Graphic)

def d2g(d: Dot)

Would plotGraphic(cd2g) cause any problems?

Safe to use a function that accepts more inputs

Subtyping Rule T => Out <: T => Out

Function Subtyping

Can substitute/use a function with another that accepts more inputs ...

If

Then

Example

Function Subtyping in Pictures

Function Subtyping in Pictures

Function Subtyping in Pictures

Function Subtyping in Pictures

Given types S and T, how can we know if S <: T ?

Subtyping Function Types

QUIZ: Function Subtyping

You tell me!

In1 => Out1 <: In2 => Out2

If:

A. In1 <: In2 and Out1 <: Out2

B. In2 <: In1 and Out1 <: Out2

C. In1 <: In2 and Out2 <: Out1

D. In2 <: In1 and Out2 <: Out1

Function Subtyping

You tell me!

In1 => Out1 <: In2 => Out2

We can use f1: In1 => Out1 in place of f2: In2 => Out2

If:

Given types S and T, how can we know if S <: T ?

Subtyping Function Types

If

Then

Given types S and T, how can we know if S <: T ?

Subtyping Function Types

If

Then

Jargon

End Digression: Back To Objects/Classes...(but don't forget functions!)

QUIZ: How can we know if S <: T ?

We know ColorDot <: Dot. How about these two (with getter methods)

class Line {
  def getP1(): Dot
  def getP2(): Dot
}

and

class ColorLine {
  def getP1(): ColorDot
  def getP2(): ColorDot
}

Is ColorLine <: Line ? (Is it OK to call foo with a ColorLine?)

A. Yes

B. No

Given types S and T, how can we know if S <: T ?

We know ColorDot <: Dot. How about these two (with getter methods)

class Line {
  def getP1(): Dot
  def getP2(): Dot
}

and

class ColorLine {
  def getP1(): ColorDot
  def getP2(): ColorDot
}

Yes we can call foo with a ColorLine

Given types S and T, how can we know if S <: T ?

We know ColorDot <: Dot. How about these two (with getter methods)

class Line {
  def getP1: () => Dot
  def getP2: () => Dot
}

class ColorLine {
  def getP1: () => ColorDot
  def getP2: () => ColorDot
}

Lets use the rules to derive ColorLine <: Line ?

Given types S and T, how can we know if S <: T ?

We know ColorDot <: Dot. How about these two (with setter methods)

class Line { 
  // other fields from before ...
  def setP1(p: Dot): Unit
  def setP2(p: Dot): Unit
}

class ColorLine {
  // other fields from before ...
  def setP1(p: ColorDot) : Unit
  def setP2(p: ColorDot) : Unit
}

Is ColorLine <: Line ?

For any function foo, can we call foo(new ColorLine(...)) ?

def foo(l: Line) {...} 

Given types S and T, how can we know if S <: T ?

OK to call foo with a ColorLine? (for any def foo(l: Line))

class Line      { def setP1(p: Dot): Unit }

class ColorLine { def setP1(p: ColorDot) : Unit }

Suppose that ColorLine <: Line ?

Consider the following foo

def foo(l: Line) { l.setP1(new Dot(0, 0)) }

Now, all hell breaks loose:

val cl     = new ColorLine
foo(cl)                     // Typechecks 
val color1 = l.getP1.color  // Runtime Error! 

But the rules would have told us this!

Given types S and T, how can we know if S <: T ?

If ColorDot <: Dot then is ColorLine <: Line ?

class Line { 
  // from before ...
  def setP1: Dot => Unit
  def setP2: Dot => Unit
}

class ColorLine {
  // from before ...
  def setP1: ColorDot => Unit
  def setP2: ColorDot => Unit
}

Fields are Not Subtypes

Recap: Subtyping = Is an object like another?

Can one widget be safely substituted with another?

Subtyping Relation S <: T

Says when a value of type S is a value of type T

Recap: Subtyping Enables Polymorphism and Reuse

Recap: Subtype Polymorphism

If S <: T then we can use S objects in place of T objects

def screen(p: Point) {
  // Only uses properties described in Point 
}

So screen can be called with Dot, ColorDot, Box, ColorBox