Subtyping,
Intersection

and

Union Types

in the simply typed lambda calculus

Subsumption

Γ ⊢ t: S
S <: T
Γ ⊢ t: T

The subsumption rule is the bridge between the typing relation and the subtyping relation

Subtype Relation

S <: U
U <: T
S <: T
S <: S

reflexivity

transitivity

Subtype relation is a preorder

Arrow Subtyping

S :> T
S' <: T'
S → S' <: T → T'
  :>
<:  

covariance

contravariance

Arrow Subtyping

example

pos → nat
nat → pos
pos → pos
nat → nat

Record Subtyping

(width) S has other fields in addition to those of T

(depth) covariant in the fields types

(permutation) S is a permutation of T

S <: T

Record Subtyping

example

{a: nat, b: pos} <: {b: pos}
{a: nat, b: pos} <: {a: nat, b: nat}
{b: pos, a: nat} <: {a: nat, b: pos}

Object Subtyping

Record subtyping is referred as structural subtyping.
Type systems of mainstream OO languages are nominal instead: subtyping is explicitly declared, the relation is defined between type names.

Object Subtyping

example

type Point2D = {x: float, y: float}
type Point3D = {x: float, y: float, z: float}
Point3D <: Point2D
type Point3D extends Point2D = {z: float}

Variants Subtyping

(width) S has fewer variants than T

(depth) covariant in the variants types

(permutation) S is a permutation of T

S <: T

Variants Subtyping

example

datatype term = Var of string
              | Lambda of (string * term)
              | App of (term * term)
              | Unit
datatype value = Lambda of (string * term)
               | Unit
value <: term

Top and Bottom

S <: Top
S :> Bot

Algorithmic Subtyping

An algorithm requires syntax-directed rules:

  • there is exactly one rule that can be applied to each syntactic form
  • there is no need to guess values to instantiate a rule

But a subtyping statement can be derived in different ways using the subtyping relation's rules.

Algorithmic Subtyping

The three rules for records can be replaced by a single rule that does width, depth and permutation subtyping all at once.

Algorithmic Subtyping

Reflexivity and transitivity apply to many inputs. Transitivity needs to guess the intermediate value.

These rules can be dropped, the rest of the subtyping rules (Top, Arrow, Record) are reflexive and transitive.

Algorithmic Subtyping

rules

↦ S <: Top
↦ S :> T
↦ S' <: T'
↦ S → S' <: T → T'

Record subtyping (widthdepth, permutation)

Algorithmic Subtyping

⊢ S <: T   ⇔   ↦ S <: T

Algorithmic Typing

Γ ⊢ t: S
S <: T
Γ ⊢ t: S

The subsumption rule is not syntax-directed

But subsumption is necessary only in application, where two types must match

Every derivation can be rewritten postponing the subsumption to any other rule (application excluded)

Algorithmic Typing

Γ ⊢ f: T → T'
Γ ⊢ t: T
Γ ⊢ f t: T'

Subsumption can be incorporated in the application rule

Γ ↦ f: T → T'
Γ ↦ t: S
Γ ↦ f t: T'
↦ S <: T

Algorithmic Typing

Γ ↦ f: Bot
Γ ↦ t: T
Γ ↦ f t: Bot
Γ ↦ r: Bot
Γ ↦ r.l: Bot

with Bottom

↦ Bot <: T

Joins

Simplistic rule definition for conditional expressions

Γ ⊢ b: Bool
Γ ⊢ t: T
Γ ⊢ if b then t else t': T
Γ ⊢ t': T

Joins

Rule definition for conditional expressions with subsumption

Γ ⊢ b: Bool
Γ ⊢ t': T'
Γ ⊢ if b then t' else t'': T
Γ ⊢ t'': T''
T' <: T
T'' <: T

However, T can be any type greater than T' and T''

Joins

A join is the least type greater than two other types
(least upper bound)

J = S ∨ T
S <: J
T <: J
∀U: S <: U, T <: U  ⇒  J <: U

Joins

A subtype relation is said to have joins if for every S and T exists a join J = S ∨ T

With the Top type, a join always exists

With multiple inheritance, two types can have more than one join

Joins

Rule definition for conditional expressions with subsumption and joins

Γ ↦ b: Bool
Γ ↦ t': T'
Γ ↦ if b then t' else t'': T
Γ ↦ t'': T''
T = T' ∨ T''

Meets

A meet is the dual of a join
(greatest lower bound)

M = S ∧ T
M <: S
M <: T
∀U: U <: S, U <: T  ⇒  U <: M

Meets

A subtype relation is said to have meets
if for every S and T exists a meet M = S ∧ T

With the Bottom type, a meet always exists

For every pair of types S and T with a common subtype, there is some type M such that M = S ∧ T

Overloading

+: (int → int → int) ∧ (string → string → string)

If ∧ represents the meet concept, then which type has +?

+: Top → Top → Bot

How can we represent its type in an useful way?

Intersection Types

The intersection type's inhabitants are terms belonging to both S and T

S ∧ T

It is an order-theoretic meet of S and T

Intersection Types

S ∧ T <: S
S ∧ T <: T
U <: S
U <: T
U <: S ∧ T

Intersection Types

(U → S) ∧ (U → T)  <:  U → (S ∧ T)
U → (S ∧ T)  <:  (U → S) ∧ (U → T)

From the previous rules the following one can be derived

To make the types equivalent, the following distributivity rule of intersection and arrow types can be added

Intersection Types

Through the Curry-Howard isomorphism,
intersection types don't correspond to logical conjunction, but product types do

In intersection types, a single proof term is a witness to two proposition, while in product types two separate terms witness the corresponding proposition

compared to product types

Intersection Types

compared to product types

int ∧ (int → int)

is uninhabited, but

int ∗ (int → int)

is inhabited, for example by (0, λx.x+3)

Intersection Types

t: T
t: T'
t: T ∧ T'
t: T
t': T'
(t,t'): T × T'
t: T ∧ T'
t: T
p: T × T'
left p: T
t: T ∧ T'
t: T'
p: T × T'
right p: T'

compared to product types

Intersection Types

With the addition of intersection types
to the simply typed lambda calculus,
type inference becomes undecidable

Union Types

The union type's inhabitants are terms belonging to S or T or both

S ∨ T

The union is not disjoint, unlike variant types,
with no tag to identify the original type of the element

Only operations common to both S and T
can be safely performed on a value of type S ∨ T

Union Types

S <: S ∨ T
T <: S ∨ T
S <: U
T <: U
S ∨ T <: U

Reference Types

Γ ⊢ t: T
Γ ⊢ ref t: Ref T
Γ ⊢ r: Ref T
Γ ⊢ !r: T
Γ ⊢ r: Ref T
Γ ⊢ r := t : Unit
Γ ⊢ t: T

Reference Types

S :> T
S <: T
Ref S <: Ref T

subtyping

Reference Types

References and intersection types' rules shown before make the type system unsound

let r = (ref 1): (nat ref) ∧ (pos ref) in
r := 0;
(!r): pos

The intersection type's introduction rule lets the type (nat ref) ∧ (pos ref) to be assigned to the value (ref 1)

and intersection types

Intersection Types

with value restriction (Davies and Pfenning)

The intersection type's introduction rule can be applied only to values

v: T
v: T'
v: T ∧ T'

Intersection Types

with value restriction (Davies and Pfenning)

The distributivity rule of intersection and arrow types is dropped, because it allows to circumvent value restriction on the introduction of intersection

Intersection Types

with value restriction (Davies and Pfenning)

Given the expression

(λx.ref 1): (unit → nat ref) ∧ (unit → pos ref)

the unrestricted introduction rule allows to type it as

(λx.ref 1): unit → (nat ref ∧ pos ref)

giving the counterexample

(λx.ref 1)(): nat ref ∧ pos ref

Intersection Types

with merge construct (Dunfield)

The value of an intersection type is duplicated and inserted into a product

let r = (ref 1, ref 1) in
(left r) := 0;
(!right r): pos

"Being sound is not the same as being useful, though"

References

Benjamin C. Pierce,
Types and Programming Languages, MIT Press, 2002

Made with Slides.com