2.2k

# Reasoning with Types

Scala exhibits quite an expressive type system, and we should leverage this type system to it's full extent. In doing so, we gain the ability to statically and provably reason about our programs. In this talk we will explore various methods on how we can use types to our advantage, as well as some hoops we must jump through to not lose this advantage in the Scala language. We will also see examples of these methods in practice by delving into some Typelevel (http://typelevel.org/) projects.

August 08, 2014

## Transcript

2. ### How the next 40 minutes are going down Assumptions Parametricity

Logic and Types Q & A

programming
4. ### Assumptions We will be subscribing to the thesis of functional

programming Functions will be total - no throwing exceptions!
5. ### 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
6. ### 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

9. ### 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!
10. ### Pop quiz: How many valid implementations are there for this?

def reverse(xs: List[Int]): List[Int]

12. ### What about reducing down a collection? def sum(xs: List[Int]): Int

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

= xs.foldLeft(0)(_ + _) Some problems:
14. ### 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!
15. ### 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?

17. None

20. ### 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!
21. ### 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]] ?
22. ### 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:
23. ### 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
24. ### 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
25. ### /** 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 }

27. ### 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] = ???
28. ### 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.
29. ### 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]

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

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

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

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

Many more: null isInstanceOf and asInstanceOf side eﬀects java.lang.Object methods

38. ### def absurd[A, B](a: A): B = absurd(a) This is the

halting problem, undecideable in the presence of Turing completeness
39. ### 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
40. ### A fancier example The idea of a “monoid” comes from

the wonderful ﬁeld of abstract algebra
41. ### A fancier example The idea of a “monoid” comes from

the wonderful ﬁeld of abstract algebra Many more cool things:
42. ### A fancier example The idea of a “monoid” comes from

the wonderful ﬁeld of abstract algebra Many more cool things: semigroup, group, semiring, ring, ﬁeld, etc.
43. ### 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]
44. ### In data mining and machine learning, dealing with vectors is

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

fairly common What sort of things do we do with vectors?
46. ### 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)
47. ### 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)
48. ### 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)
49. ### 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)
50. ### The naive way of doing things case class Module(repr: Vector[Double])

{ def +(other: Module): Module def -(other: Module): Module def *:(s: Double): Module }

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

We only care we can add, subtract, and multiply the “inner” type!
53. ### 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] }

55. ### What if we want scalar division? Rngs support negation, addition,

and multiplication, but not division
56. ### 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!
57. ### 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] }
58. ### Logic and Types There is a profound relationship between proofs

and computation
59. ### Logic and Types There is a profound relationship between proofs

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

and computation Curry-Howard correspondence/isomorphism More expressive types = more expressive proofs
61. ### 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
62. ### 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 }
63. ### 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!
64. ### Example: Get the head of a list v.2 def head[A](as:

List[A]): A = as match { case Nil => null case a :: _ => a }
65. ### 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..
66. ### The evil that is null Tony Hoare calls null his

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

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

“billion dollar mistake” null inhabits every type What this means for Curry-Howard:
69. ### 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
70. ### 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:
71. ### 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
72. ### Example: Get the head of a list v.3 What are

we trying to do?
73. ### 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
74. ### 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]
75. ### 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
76. ### 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”
77. ### 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) }
78. ### Example: Get the head of a list v.4 “But I

know my list will never be empty!”
79. ### 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
80. ### 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]]
81. ### 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
82. ### 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?
83. ### 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 }
84. ### 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]
85. ### 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 }
86. ### def sum[A](as: List[A])(implicit A: Monoid[A]): A The implicit parameter Monoid[A]

is analogous to proof search

3))
88. ### 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 ﬁnd proof Int forms a Monoid (no Monoid[Int] in scope!)
89. ### 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))
90. ### 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
91. ### 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
92. ### 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
93. ### 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:
94. ### 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
95. ### 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
96. ### 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:
97. ### 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)
98. ### 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
99. ### In the presence of a non-dependently typed type system, we

cannot statically enforce laws
100. ### 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)
101. ### 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
102. ### 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
103. ### 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 speciﬁed by types and their properties
104. ### Summary Types are robust, a form of documentation, and not

just “something for the compiler”
105. ### 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
106. ### 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
107. ### 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 diﬀerence:
108. ### 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 diﬀerence: “Trust me I’m a nice person I will/won’t do that”
109. ### 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 diﬀerence: “Trust me I’m a nice person I will/won’t do that” “It (did not) compile(d)”