Objects

Structural Object Types

So...

What is a Type?

What is a Type?

Say we have a function of type T => U

val foo : T => U = x => ...

and an object of type S.

val o : S = { ... }
foo(o)

Subtype Polymorphism

When can S-typed values can be substituted for T-typed values?

If

Then

Okay, So When is S <: T ?

Recall ticker Type From Last Time




type t = { def next(): Any
         ; def hasNext: Boolean }

Is s <: t?

type s = { def hasNext: Boolean
         ; def next(): Any }

type t = { def next(): Any
         ; def hasNext: Boolean }

Is s <: t?

type s = { def next(): Any
         ; def hasNext: Boolean }

type t = { def hasNext: Boolean
         ; def next(): Any;
         ; def foreach(f: Any => Unit): Unit }

CLICKER: Is s <: t?

type s = { def hasNext: String
         ; def next(): Any;
         ; def foreach(f: Any => Unit): Unit }

type t = { def next(): Any
         ; def hasNext: Boolean }

A: Yes B: No

CLICKER: Is s <: t?

type s = { def hasNext: String
         ; def next(): Any;
         ; def foreach(f: Any => Unit): Unit }

type t = { def next(): Any
         ; def hasNext: Boolean }

"Width" Subtyping Rule

When structural type S has more attributes than T

If

Then

CLICKER: Is s <: t?

type s = { def next(): Int;
         ; def hasNext: Boolean
         ; def foreach(f: Any => Unit): Unit }

type t = { def next(): Any
         ; def hasNext: Boolean }

A: Yes B: No

CLICKER: Is s <: t?

type s = { def next(): Int;
         ; def hasNext: Boolean
         ; def foreach(f: Any => Unit): Unit }

type t = { def next(): Any
         ; def hasNext: Boolean }

"Depth" Subtyping Rule

When S fields are subtypes of T fields

If

Then

RECAP: Structural Object Types

So far, three subtyping rules:

  1. Permutation : order of members doesn't matter

  2. Width : extra members are okay

  3. Depth : member types can be "more specific" than required

A Couple Simple Rules

Reflexivity Rule

The subtyping (or "is-a") relation is reflexive

For all T

Transitivity Rule

The subtyping (or "is-a") relation is transitive

If

Then

Subtyping "Queries"

Subtyping "Queries"

How can we ask Scala if an expression e has a certain type T?

Subtyping "Queries"

How can we ask Scala if an expression e has a certain type T?

scala> val blah: T = e

Subtyping "Queries"

scala> val blah: Int = 1
blah: Int = 1

scala> val blah: Boolean = 1
<console>:7: error: type mismatch;
 found   : Int(1)
 required: Boolean
       val blah: Boolean = 1
                           ^

scala> val blah: Any = 1
blah: Any = 1

Type Ascription

Can make these "subtyping queries" more directly:

scala> (1:Int, 1:Any)
res20: (Int, Any) = (1, 1)

scala> 1:Boolean
... error ...

What About val/var/def Declarations?

Member Declaration Kinds

type t1 = { val i : Int } ; type t3 = { def i   : Int } 
type t2 = { var i : Int } ; type t4 = { def i() : Int } 

Member Declaration Kinds

type t1 = { val i : Int } ; type t3 = { def i   : Int } 
type t2 = { var i : Int } ; type t4 = { def i() : Int } 

object o1 { val i = 0   } ; object o3 { def i   = 0   }
object o2 { var i = 0   } ; object o4 { def i() = 0   }

CLICKER: Subtyping for Declaration Kinds

type t1 = { val i : Int } ; type t3 = { def i   : Int } 
type t2 = { var i : Int } ; type t4 = { def i() : Int } 

object o1 { val i = 0   } ; object o3 { def i   = 0   }
object o2 { var i = 0   } ; object o4 { def i() = 0   }

scala> o1:t2     // Is this expression well-typed?

A: Yes B: No

CLICKER: Subtyping for Declaration Kinds

type t1 = { val i : Int } ; type t3 = { def i   : Int } 
type t2 = { var i : Int } ; type t4 = { def i() : Int } 

object o1 { val i = 0   } ; object o3 { def i   = 0   }
object o2 { var i = 0   } ; object o4 { def i() = 0   }

scala> o1:t3     // Is this expression well-typed?

A: Yes B: No

CLICKER: Subtyping for Declaration Kinds

type t1 = { val i : Int } ; type t3 = { def i   : Int } 
type t2 = { var i : Int } ; type t4 = { def i() : Int } 

object o1 { val i = 0   } ; object o3 { def i   = 0   }
object o2 { var i = 0   } ; object o4 { def i() = 0   }

scala> o1:t4     // Is this expression well-typed?

A: Yes B: No

Subtyping for Declaration Kinds

type t1 = { val i : Int } ; type t3 = { def i   : Int } 
type t2 = { var i : Int } ; type t4 = { def i() : Int } 

object o1 { val i = 0   } ; object o3 { def i   = 0   }
object o2 { var i = 0   } ; object o4 { def i() = 0   }

scala> o2:t1     // Is this expression well-typed?

Subtyping for Declaration Kinds

Fields

Methods with No Parameter List

Methods with Empty Parameter List

So Far: Structural Object Types

What About Classes?

Subtyping with Classes

Two structurally identically classes:

class C1 { var i:Int = 0 } ; class C2 { var i:Int = 0 } 
val x1 = new C1            ; val x2 = new C2 

Subtyping with Classes

Two structurally identically classes:

class C1 { val i:Int = 0 } ; class C2 { val i:Int = 0 } 
val x1 = new C1            ; val x2 = new C2 
scala> x1:C2
<console>:11: error: type mismatch;
 found   : C1
 required: C2

A Tale of Two Kinds of Objects

Nominal Subtyping

When one class instance is an instance of another class

If

Then

Nominal Subtyping

class C1 { val i:Int = 0 } ; class C2 { val i:Int = 0 } 

Benefits of Nominal Types

Any ideas?

Benefits of Nominal Types

1. Easy to Implement Subtyping

Benefits of Nominal Types

1. Easy to Implement Subtyping

2. Prevents "Accidental" Subtyping

Benefits of Nominal Types

1. Easy to Implement Subtyping

2. Prevents "Accidental" Subtyping

3. Recursive Types are Very Easy

abstract class IntList
  { val data: Int; val next: Option[IntList] } 

So Much Tension...

Traits

Subtyping with Traits

Subtyping with Traits

Option 1: Define Classes to Extend Traits

trait inti { val i : Int }
class D1 extends inti { val i = 0 }
class D2 extends inti { val i = 0 }
val y1 = new D1 ; val y2 = new D2

Subtyping with Traits

Option 2: Mix in Trait with Different, Unrelated Classes

class C1 { val i = 0 } ; class C2 { val i = 0 }
trait inti { val i : Int }

val z1 = new C1 with inti  ; val z2 = new C2 with inti
 // z1 : C1 with inti         // z2 : C2 with inti

Note: the types of z1 and z2 mention with.

Things are (Even) More Complicated
Than They Seem...

Subtyping Between Nominal and Structural

class C1 { val i:Int = 0 }
val x1 = new C1

scala> x1:{val i:Int}
res60: AnyRef{val i: Int} = C1@13c59de

scala> x1:{}
res61: AnyRef{} = C1@1b95cf5

What About Functions?

CLICKER: What's the result?

def applyFunc(f: Double => Double) = 3.14 + (f (3.14))
def addOne(i: Int)                 = 1 + i

val res = applyFunc(addOne)

A: Type Error (in applyFunc)

B: Type Error (in call to applyFunc)

C: res: Int = 7

D: res: Double = 7.28

E: Run-Time Exception

Subtyping (continued), Mar 11

Function Subtyping

def applyFunc(f: Double => Double) = 3.14 + (f (3.14))
def addOne(i: Int)                 = 1 + i

val res = applyFunc(addOne)

Function Subtyping: Return Type

When is (S => T1) <: (S => T2) ?

Function Subtyping: Argument Type

When is (S1 => T) <: (S2 => T) ?

Function Subtyping Rule

When is (In1 => Out1) <: (In2 => Out2) ?

If

Then

Jargon

Function Subtyping in Pictures

Courtesy: Ranjit Jhala

Function Subtyping in Pictures

Courtesy: Ranjit Jhala

Function Subtyping in Pictures

Courtesy: Ranjit Jhala

Function Subtyping in Pictures

Courtesy: Ranjit Jhala

Function Subtyping

def applyFunc(f: Double => Double) = 3.14 + (f (3.14))
def addOne(i: Int)                 = 1 + i

val res = applyFunc(addOne)

Let's Combine Objects and Functions

Classes and Methods and Types, Oh My!

class Parent { def foo(p:Parent) = () }

class Child extends Parent
  { override def foo(p:Parent) = () }

Classes and Methods and Types, Oh My!

class Parent { def foo(p:Parent) = () }

class ProblemChild extends Parent
  { override def foo(c:ProblemChild) = () }

RECAP: Subtyping

Basic Rules

Structural Types

Nominal Types

Function Types

And We Haven't Thrown in
Type Variables Yet...