Slide 1

Slide 1 text

Reasoning with Types Adelbert Chang Software Engineer @ Box Inc.

Slide 2

Slide 2 text

How the next 40 minutes are going down Assumptions Parametricity Logic and Types Q & A

Slide 3

Slide 3 text

Assumptions We will be subscribing to the thesis of functional programming

Slide 4

Slide 4 text

Assumptions We will be subscribing to the thesis of functional programming Functions will be total - no throwing exceptions!

Slide 5

Slide 5 text

Assumptions We will be subscribing to the thesis of functional programming Functions will be total - no throwing exceptions! Every input will relate to exactly one output

Slide 6

Slide 6 text

Assumptions We will be subscribing to the thesis of functional programming Functions will be total - no throwing exceptions! Every input will relate to exactly one output We will require expressions to be referentially transparent - we can replace expressions with their value without altering the behavior of the program

Slide 7

Slide 7 text

Parametricity Parametric polymorphism In 1989 Philip Wadler wrote a popular paper called “Theorems for free!”

Slide 8

Slide 8 text

Parametricity Parametric polymorphism In 1989 Philip Wadler wrote a popular paper called “Theorems for free!” Types are documentation

Slide 9

Slide 9 text

Parametricity Parametric polymorphism In 1989 Philip Wadler wrote a popular paper called “Theorems for free!” Types are documentation Docstrings erode, types are forever and compiler checked!

Slide 10

Slide 10 text

Pop quiz: How many valid implementations are there for this? def reverse(xs: List[Int]): List[Int]

Slide 11

Slide 11 text

How about now? def reverse[A](xs: List[A]): List[A]

Slide 12

Slide 12 text

What about reducing down a collection? def sum(xs: List[Int]): Int = xs.foldLeft(0)(_ + _)

Slide 13

Slide 13 text

What about reducing down a collection? def sum(xs: List[Int]): Int = xs.foldLeft(0)(_ + _) Some problems:

Slide 14

Slide 14 text

What about reducing down a collection? def sum(xs: List[Int]): Int = xs.foldLeft(0)(_ + _) Some problems: We can still cheat with our knowledge of Int!

Slide 15

Slide 15 text

What about reducing down a collection? def sum(xs: List[Int]): Int = xs.foldLeft(0)(_ + _) Some problems: We can still cheat with our knowledge of Int! Not generic - what if we wanted Double? Long? BigInt?

Slide 16

Slide 16 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus)

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus)

Slide 19

Slide 19 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus) Some problems:

Slide 20

Slide 20 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus) Some problems: Still can manifest terms of A with Numeric#fromInt!

Slide 21

Slide 21 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus) Some problems: Still can manifest terms of A with Numeric#fromInt! Still not quite generic - what if we reduce List[String] ? List[Set[A]] ?

Slide 22

Slide 22 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus) Some problems: Still can manifest terms of A with Numeric#fromInt! Still not quite generic - what if we reduce List[String] ? List[Set[A]] ? What do we actually need:

Slide 23

Slide 23 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus) Some problems: Still can manifest terms of A with Numeric#fromInt! Still not quite generic - what if we reduce List[String] ? List[Set[A]] ? What do we actually need: A “zero” value of type A for the empty list case

Slide 24

Slide 24 text

def sum[A](xs: List[A])(implicit A: Numeric[A]): A = xs.foldLeft(A.zero)(A.plus) Some problems: Still can manifest terms of A with Numeric#fromInt! Still not quite generic - what if we reduce List[String] ? List[Set[A]] ? What do we actually need: A “zero” value of type A for the empty list case A binary operation (A, A) => A to accumulate

Slide 25

Slide 25 text

/** In Scalaz and Spire */ trait Monoid[A] { def append(x: A, y: A): A def zero: A } /** Example of a Monoid[Int] */ new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 }

Slide 26

Slide 26 text

def sum[A](xs: List[A])(implicit A: Monoid[A]): A = xs.foldLeft(A.zero)(A.append)

Slide 27

Slide 27 text

We can go further (up the ivory tower).. This argument applies to functors, applicatives, monads, etc. def foo[A, B, C](as: List[A]) (f: A => B)(g: B => C): List[C] = ???

Slide 28

Slide 28 text

This argument applies to functors, applicatives, monads, etc. def foo[A, B, C](as: List[A]) (f: A => B)(g: B => C): List[C] = List.empty[C] as.map(f andThen g) as.reverse.map(f andThen g) etc.

Slide 29

Slide 29 text

This argument applies to functors, applicatives, monads, etc. trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } def foo[F[_] : Functor, A, B, C] (fa: F[A])(f: A => B)(g: B => C): F[C]

Slide 30

Slide 30 text

Parametricity sadness def absurd[A, B](a: A): B = ???

Slide 31

Slide 31 text

def absurd[A, B](a: A): B = a.asInstanceOf[B] throw new Exception("oops")

Slide 32

Slide 32 text

def absurd[A, B](a: A): B = a.asInstanceOf[B] throw new Exception("oops") Many more:

Slide 33

Slide 33 text

def absurd[A, B](a: A): B = a.asInstanceOf[B] throw new Exception("oops") Many more: null

Slide 34

Slide 34 text

def absurd[A, B](a: A): B = a.asInstanceOf[B] throw new Exception("oops") Many more: null isInstanceOf and asInstanceOf

Slide 35

Slide 35 text

def absurd[A, B](a: A): B = a.asInstanceOf[B] throw new Exception("oops") Many more: null isInstanceOf and asInstanceOf side effects

Slide 36

Slide 36 text

def absurd[A, B](a: A): B = a.asInstanceOf[B] throw new Exception("oops") Many more: null isInstanceOf and asInstanceOf side effects java.lang.Object methods

Slide 37

Slide 37 text

def absurd[A, B](a: A): B = absurd(a)

Slide 38

Slide 38 text

def absurd[A, B](a: A): B = absurd(a) This is the halting problem, undecideable in the presence of Turing completeness

Slide 39

Slide 39 text

def absurd[A, B](a: A): B = absurd(a) This is the halting problem, undecideable in the presence of Turing completeness Many (theorem proving) languages give up Turing completeness in favor of totality checking

Slide 40

Slide 40 text

A fancier example The idea of a “monoid” comes from the wonderful field of abstract algebra

Slide 41

Slide 41 text

A fancier example The idea of a “monoid” comes from the wonderful field of abstract algebra Many more cool things:

Slide 42

Slide 42 text

A fancier example The idea of a “monoid” comes from the wonderful field of abstract algebra Many more cool things: semigroup, group, semiring, ring, field, etc.

Slide 43

Slide 43 text

Welcome to Spire trait Semigroup[A] trait Monoid[A] extends Semigroup[A] trait Group[A] extends Monoid[A] trait Semiring[A] extends ... trait Rig[A] extends Semiring[A] trait Rng[A] extends Semiring[A] trait Ring[A] extends Rig[A] with Rng[A] trait Field[A] extends Ring[A]

Slide 44

Slide 44 text

In data mining and machine learning, dealing with vectors is fairly common

Slide 45

Slide 45 text

In data mining and machine learning, dealing with vectors is fairly common What sort of things do we do with vectors?

Slide 46

Slide 46 text

In data mining and machine learning, dealing with vectors is fairly common What sort of things do we do with vectors? addition, subtraction, scalar multiplication (module)

Slide 47

Slide 47 text

In data mining and machine learning, dealing with vectors is fairly common What sort of things do we do with vectors? addition, subtraction, scalar multiplication (module) scalar division (vector space)

Slide 48

Slide 48 text

In data mining and machine learning, dealing with vectors is fairly common What sort of things do we do with vectors? addition, subtraction, scalar multiplication (module) scalar division (vector space) normalization (normed vector space)

Slide 49

Slide 49 text

In data mining and machine learning, dealing with vectors is fairly common What sort of things do we do with vectors? addition, subtraction, scalar multiplication (module) scalar division (vector space) normalization (normed vector space) dot product (inner product space)

Slide 50

Slide 50 text

The naive way of doing things case class Module(repr: Vector[Double]) { def +(other: Module): Module def -(other: Module): Module def *:(s: Double): Module }

Slide 51

Slide 51 text

Why specialize to Double? In fact, why specialize to Vector[_]?

Slide 52

Slide 52 text

Why specialize to Double? In fact, why specialize to Vector[_]? We only care we can add, subtract, and multiply the “inner” type!

Slide 53

Slide 53 text

Why specialize to Double? In fact, why specialize to Vector[_]? We only care we can add, subtract, and multiply the “inner” type! Put another way, we care it forms a Rng! trait Rng[A] { def negate(x: A): A def plus(x: A, y: A): A def times(x: A, y: A): A def zero: A } trait Module[V, R] { implicit abstract def scalar: Rng[R] }

Slide 54

Slide 54 text

What if we want scalar division?

Slide 55

Slide 55 text

What if we want scalar division? Rngs support negation, addition, and multiplication, but not division

Slide 56

Slide 56 text

What if we want scalar division? Rngs support negation, addition, and multiplication, but not division Divison is more power - rationals are closed over division, integers are not!

Slide 57

Slide 57 text

What if we want scalar division? Rngs support negation, addition, and multiplication, but not division Divison is more power - rationals are closed over division, integers are not! Abstract algebra and Spire give us Field trait Field[A] extends Rng[A] { ... } trait VectorSpace[V, R] extends Module[V, R] { implicit abstract def scalar: Field[R] }

Slide 58

Slide 58 text

Logic and Types There is a profound relationship between proofs and computation

Slide 59

Slide 59 text

Logic and Types There is a profound relationship between proofs and computation Curry-Howard correspondence/isomorphism

Slide 60

Slide 60 text

Logic and Types There is a profound relationship between proofs and computation Curry-Howard correspondence/isomorphism More expressive types = more expressive proofs

Slide 61

Slide 61 text

Logic and Types There is a profound relationship between proofs and computation Curry-Howard correspondence/isomorphism More expressive types = more expressive proofs How the correspondence works: Logic Computation proposition P type P P is provable P is inhabited proof of P term of type P P and Q product type (P, Q) P or Q sum type P | Q if P then Q P → Q

Slide 62

Slide 62 text

Example: Get the head of a list v.1 def head[A](as: List[A]): A = as match { case Nil => throw new Exception("oops") case a :: _ => a }

Slide 63

Slide 63 text

Example: Get the head of a list v.1 def head[A](as: List[A]): A = as match { case Nil => throw new Exception("oops") case a :: _ => a } Problem: head is partial!

Slide 64

Slide 64 text

Example: Get the head of a list v.2 def head[A](as: List[A]): A = as match { case Nil => null case a :: _ => a }

Slide 65

Slide 65 text

Example: Get the head of a list v.2 def head[A](as: List[A]): A = as match { case Nil => null case a :: _ => a } Problem: null is evil..

Slide 66

Slide 66 text

The evil that is null Tony Hoare calls null his “billion dollar mistake”

Slide 67

Slide 67 text

The evil that is null Tony Hoare calls null his “billion dollar mistake” null inhabits every type

Slide 68

Slide 68 text

The evil that is null Tony Hoare calls null his “billion dollar mistake” null inhabits every type What this means for Curry-Howard:

Slide 69

Slide 69 text

The evil that is null Tony Hoare calls null his “billion dollar mistake” null inhabits every type What this means for Curry-Howard: Any proposition can be proved, trivially

Slide 70

Slide 70 text

The evil that is null Tony Hoare calls null his “billion dollar mistake” null inhabits every type What this means for Curry-Howard: Any proposition can be proved, trivially What this means computationally:

Slide 71

Slide 71 text

The evil that is null Tony Hoare calls null his “billion dollar mistake” null inhabits every type What this means for Curry-Howard: Any proposition can be proved, trivially What this means computationally: Developers are left to their own devices on handling null = very brittle code

Slide 72

Slide 72 text

Example: Get the head of a list v.3 What are we trying to do?

Slide 73

Slide 73 text

Example: Get the head of a list v.3 What are we trying to do? Given a List[A], I may or may not be able to prove A

Slide 74

Slide 74 text

Example: Get the head of a list v.3 What are we trying to do? Given a List[A], I may or may not be able to prove A How do we deal with failure if not with exceptions and nulls? sealed abstract class Option[A] final case class None[A]() extends Option[A] final case class Some[A](a: A) extends Option[A]

Slide 75

Slide 75 text

Example: Get the head of a list v.3 What are we trying to do? Given a List[A], I may or may not be able to prove A How do we deal with failure if not with exceptions and nulls? sealed abstract class Option[A] final case class None[A]() extends Option[A] final case class Some[A](a: A) extends Option[A] The number of inhabitants of Option[A] is precisely the number of inhabitants of A + 1

Slide 76

Slide 76 text

Example: Get the head of a list v.3 What are we trying to do? Given a List[A], I may or may not be able to prove A How do we deal with failure if not with exceptions and nulls? sealed abstract class Option[A] final case class None[A]() extends Option[A] final case class Some[A](a: A) extends Option[A] The number of inhabitants of Option[A] is precisely the number of inhabitants of A + 1 This “+ 1” represents our “I couldn’t prove it”

Slide 77

Slide 77 text

Example: Get the head of a list v.3 def head[A](xs: List[A]): Option[A] = as match { case Nil => None case a :: _ => Some(a) }

Slide 78

Slide 78 text

Example: Get the head of a list v.4 “But I know my list will never be empty!”

Slide 79

Slide 79 text

Example: Get the head of a list v.4 “But I know my list will never be empty!” Really? Curry-Howard says prove it! case class NonEmptyList[A](head: A, tail: List[A]) def head[A](as: NonEmptyList[A]): A = as.head

Slide 80

Slide 80 text

A use case for NonEmptyList /** Both NonEmptyList and Validation are in Scalaz */ case class NonEmptyList[A](head: A, tail: List[A]) type ValidationNel[E, A] = Validation[NonEmptyList[E], A] def groupBy[A, B](as: List[A]) (f: A => B): Map[B, NonEmptyList[A]]

Slide 81

Slide 81 text

Curry-Howard and type classes def sum[A](as: List[A])(implicit A: Monoid[A]): A Given a List[A] where A forms a monoid, I can give you (prove) A

Slide 82

Slide 82 text

Curry-Howard and type classes def sum[A](as: List[A])(implicit A: Monoid[A]): A Given a List[A] where A forms a monoid, I can give you (prove) A How do you prove some type A forms a monoid?

Slide 83

Slide 83 text

Curry-Howard and type classes def sum[A](as: List[A])(implicit A: Monoid[A]): A Given a List[A] where A forms a monoid, I can give you (prove) A How do you prove some type A forms a monoid? Propositions are types! trait Monoid[A] { def append(x: A, y: A): A def zero: A }

Slide 84

Slide 84 text

Curry-Howard and type classes def sum[A](as: List[A])(implicit A: Monoid[A]): A Given a List[A] where A forms a monoid, I can give you (prove) A How do you prove some type A forms a monoid? Propositions are types! trait Monoid[A] { def append(x: A, y: A): A def zero: A } Prove A is a monoid by creating a term of type Monoid[A]

Slide 85

Slide 85 text

Curry-Howard and type classes /** Proposition of a monoid */ trait Monoid[A] { def append(x: A, y: A): A def zero: A } /** Proof Int forms a monoid */ new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 }

Slide 86

Slide 86 text

def sum[A](as: List[A])(implicit A: Monoid[A]): A The implicit parameter Monoid[A] is analogous to proof search

Slide 87

Slide 87 text

import scalaz.Monoid def sum[A](as: List[A])(implicit A: Monoid[A]): A sum(List(1, 2, 3))

Slide 88

Slide 88 text

import scalaz.Monoid def sum[A](as: List[A])(implicit A: Monoid[A]): A sum(List(1, 2, 3)) error: could not find implicit value for evidence parameter sum(List(1,2,3)) ^ Read: Could not find proof Int forms a Monoid (no Monoid[Int] in scope!)

Slide 89

Slide 89 text

import scalaz.Monoid import scalaz.std.int._ // import proof for Int def sum[A](as: List[A])(implicit A: Monoid[A]): A sum(List(1, 2, 3))

Slide 90

Slide 90 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics

Slide 91

Slide 91 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws

Slide 92

Slide 92 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws Said type classes should have instances which abide by these laws

Slide 93

Slide 93 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws Said type classes should have instances which abide by these laws Example: monoids have:

Slide 94

Slide 94 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws Said type classes should have instances which abide by these laws Example: monoids have: An associative binary operation

Slide 95

Slide 95 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws Said type classes should have instances which abide by these laws Example: monoids have: An associative binary operation A zero that is an identity in relation to the binary operation

Slide 96

Slide 96 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws Said type classes should have instances which abide by these laws Example: monoids have: An associative binary operation A zero that is an identity in relation to the binary operation This is useful because:

Slide 97

Slide 97 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws Said type classes should have instances which abide by these laws Example: monoids have: An associative binary operation A zero that is an identity in relation to the binary operation This is useful because: It further restricts the behavior of the type class (aids parametricity)

Slide 98

Slide 98 text

Following the law new Monoid[Int] { def append(x: Int, y: Int): Int = x + y def zero: Int = 0 } Many type classes in libraries such as Scalaz and Spire are inspired by mathematics Mathematics is very much governed by laws Said type classes should have instances which abide by these laws Example: monoids have: An associative binary operation A zero that is an identity in relation to the binary operation This is useful because: It further restricts the behavior of the type class (aids parametricity) Allows us to exploit these laws

Slide 99

Slide 99 text

In the presence of a non-dependently typed type system, we cannot statically enforce laws

Slide 100

Slide 100 text

In the presence of a non-dependently typed type system, we cannot statically enforce laws However, laws are perfect for property-based testing (ScalaCheck and Discipline)

Slide 101

Slide 101 text

In the presence of a non-dependently typed type system, we cannot statically enforce laws However, laws are perfect for property-based testing (ScalaCheck and Discipline) ∀(x : Int, y : Int, z : Int), x + (y + z) = (x + y) + z

Slide 102

Slide 102 text

In the presence of a non-dependently typed type system, we cannot statically enforce laws However, laws are perfect for property-based testing (ScalaCheck and Discipline) ∀(x : Int, y : Int, z : Int), x + (y + z) = (x + y) + z ∀(x : Int), x + 0 = 0 + x = x

Slide 103

Slide 103 text

In the presence of a non-dependently typed type system, we cannot statically enforce laws However, laws are perfect for property-based testing (ScalaCheck and Discipline) ∀(x : Int, y : Int, z : Int), x + (y + z) = (x + y) + z ∀(x : Int), x + 0 = 0 + x = x Our code now becomes even more strictly specified by types and their properties

Slide 104

Slide 104 text

Summary Types are robust, a form of documentation, and not just “something for the compiler”

Slide 105

Slide 105 text

Summary Types are robust, a form of documentation, and not just “something for the compiler” Types hint at what a function can do, but more importantly tell us what it cannot do

Slide 106

Slide 106 text

Summary Types are robust, a form of documentation, and not just “something for the compiler” Types hint at what a function can do, but more importantly tell us what it cannot do Types are a static property of your program

Slide 107

Slide 107 text

Summary Types are robust, a form of documentation, and not just “something for the compiler” Types hint at what a function can do, but more importantly tell us what it cannot do Types are a static property of your program Recognize the difference:

Slide 108

Slide 108 text

Summary Types are robust, a form of documentation, and not just “something for the compiler” Types hint at what a function can do, but more importantly tell us what it cannot do Types are a static property of your program Recognize the difference: “Trust me I’m a nice person I will/won’t do that”

Slide 109

Slide 109 text

Summary Types are robust, a form of documentation, and not just “something for the compiler” Types hint at what a function can do, but more importantly tell us what it cannot do Types are a static property of your program Recognize the difference: “Trust me I’m a nice person I will/won’t do that” “It (did not) compile(d)”

Slide 110

Slide 110 text

Questions? @adelbertchang