2.3k

# 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

1. Reasoning with Types
Software Engineer @ Box Inc.

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

3. Assumptions
We will be subscribing to the thesis of functional 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

7. Parametricity
Parametric polymorphism
In 1989 Philip Wadler wrote a popular paper called “Theorems

8. Parametricity
Parametric polymorphism
In 1989 Philip Wadler wrote a popular paper called “Theorems
Types are documentation

9. Parametricity
Parametric polymorphism
In 1989 Philip Wadler wrote a popular paper called “Theorems
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]

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

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?

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

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

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

19. 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!

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!
Still not quite generic - what if we reduce List[String] ?
List[Set[A]] ?

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]] ?
What do we actually need:

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:
A “zero” value of type A for the empty list case

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
A binary operation (A, A) => A to accumulate

24. /** 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
}

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

26. 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] = ???

27. 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.

28. 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]

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

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

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

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

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

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

35. 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

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

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

38. 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

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

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

41. 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.

42. 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]

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

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

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?
scalar division (vector space)

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

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

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

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

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

52. 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]
}

53. What if we want scalar division?

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

55. 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!

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!
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]
}

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

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

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

60. 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

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

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

63. Example: Get the head of a list v.2
as match {
case Nil => null
case a :: _ => a
}

64. Example: Get the head of a list v.2
as match {
case Nil => null
case a :: _ => a
}
Problem: null is evil..

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

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

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

68. 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

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
What this means computationally:

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:
Developers are left to their own devices on handling null =
very brittle code

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

72. 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

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
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]

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]
The number of inhabitants of Option[A] is precisely the
number of inhabitants of A + 1

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
This “+ 1” represents our “I couldn’t prove it”

76. Example: Get the head of a list v.3
as match {
case Nil => None
case a :: _ => Some(a)
}

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

78. 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])

79. 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]]

80. 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

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
How do you prove some type A forms a monoid?

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?
Propositions are types!
trait Monoid[A] {
def append(x: A, y: A): A
def zero: A
}

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
}
Prove A is a monoid by creating a term of type Monoid[A]

84. 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
}

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

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

87. 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!)

88. 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))

89. 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

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
Mathematics is very much governed by laws

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
Said type classes should have instances which abide by these
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
Example: monoids have:

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:
An associative binary operation

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
A zero that is an identity in relation to the 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
This is useful because:

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:
It further restricts the behavior of the type class (aids
parametricity)

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)
Allows us to exploit these laws

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

99. 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)

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)
∀(x : Int, y : Int, z : Int), x + (y + z) = (x + y) + z

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
∀(x : Int), x + 0 = 0 + x = x

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
Our code now becomes even more strictly speciﬁed by types
and their properties

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

104. 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

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
Types are a static property of your program

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
Recognize the diﬀerence:

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:
“Trust me I’m a nice person I will/won’t do that”

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”
“It (did not) compile(d)”

109. Questions?