Shannon
March 28, 2019
190

# Daniel Ciocîrlan - Type-Level Sorting in Scala

Abstract: Scala's type system and implicit resolution mechanism allow for sophisticated type-level relationship inference. In this talk, we will see how we can impose type-level constraints and determine complex relationships between types, enough to be able to sort types... at compile time.

March 28, 2019

## Transcript

1. Rock the JVM!
with Daniel Ciocîrlan

2. Type-Level
Arithmetic
in Scala
Daniel Ciocîrlan
rockthejvm.com

3. Goal
Show the power of Scala’s type system and implicit resolution
Prerequisites
• Scala implicits
• Type aliases
• A little bit of math…
Operate on TYPES at compile time
Note: I work at Palantir, but this talk is completely unrelated to my work there.

4. trait Nat
class _0 extends Nat
class Succ[N <: Nat] extends Nat
Ep 1: numbers as types
We can’t define all natural numbers ”manually”
Define natural numbers in terms of their succession
object Nat {
type _1 = Succ[_0]
type _2 = Succ[_1]
type _3 = Succ[_2]
type _4 = Succ[_3]
type _5 = Succ[_4]
...
}

5. class <[A <: Nat, B <: Nat]
Ep 2: type ”comparison”
We just kissed regular values goodbye
Define the less-than relationship as a type
realistic name
<[_0,_1] is a type, not an expression
_0 < _1 is sugar for <[_0,_1]
Axioms of less-than:
• _0 < Succ[N] for every natural N
• Succ[A] < Succ[B] if and only if A < B

6. val x: _2 < _4 = ...
Ep 2: type ”comparison”
The compiler should figure out if the type _2 < _4 exists :
• that’s the same as Succ[_1] < Succ[_3]
• does the first axiom work? no.
• second axiom: Succ[_1] < Succ[3] exists only if _1 < _3 exists
• same as Succ[_0] < Succ[_2]
• does the first axiom work? no.
• second axiom: Succ[_0] < Succ[_2] exists only if _0 < _2 exists
• does the first axiom work?
• YES!
• so _0 < _2 exists
• so _1 < _3 exists
• so _2 < _4 exists! The type is fine!
How do we figure out if a type exists ? Through implicits.

7. implicit def basicAxiom[N <: Nat] = new <[_0, Succ[N]]
Ep 2: type ”comparison”
object < {
implicit def basicAxiom[N <: Nat] = new <[_0, Succ[N]]
implicit def inductiveAxiom[A <: Nat, B <: Nat](implicit evidence: A < B) = new <[Succ[A], Succ[B]]
}
Read: for every type N derived from Nat, there is an instance of _0 < Succ[N].
implicit def inductiveAxiom[A <: Nat, B <: Nat](implicit evidence: A < B) = new <[Succ[A], Succ[B]]
Read: for every type A and B derived from Nat,
• if there is an instance of A < B
• then there is an instance of Succ[A] < Succ[B].
same as <[A, B]

8. We’re starting to read implicits a little differently...

trait +[A <: Nat, B <: Nat] {
type Result <: Nat
}
type Add[A <: Nat, B <: Nat, R <: Nat] = +[A, B] {
type Result = R
}
Read: if someone gives me an instance of Add, I’ll consider it a + and I’ll store the type member
Read: the type of an add operation of two numbers with the result stored as a type member
(we’ll need this later)

Axiom 1: adding 0 gives back the same number
Read: for every number, there is an instance of an add operation with zero and that number
implicit def basic[A <: Nat]: Add[_0, A, A] = new +[_0, A] {
type Result = A
}
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: +[A, B]): Add[Succ[A], B, Succ[evidence.Result]] =
new +[Succ[A], B] {
type Out = Succ[evidence.Result]
}
Axiom 2: adding two random numbers
Read: for every two ”numbers” A and B,
if there is an instance of an add operation on A and B,
• then there is an instance of an add operation on Succ[A] and B where
• the result is the successor of adding A and B

Bringing the evidence to the surface
(so we can see it clearly when we print its type tag)
def apply[A <: Nat, B <: Nat](implicit evidence: +[A, B]): new +[A, B] {
type Result = evidence.Result
}
def show[T](implicit tag: TypeTag[A]) = println(tag)
show(+[_2, _3])
TypeTag[... {
type Result = Succ[Succ[Succ[Succ[Succ[_0]]]]]
}]

But how?
Is there an implicit value of type +[_2, _3]?
show(+[_2, _3])
implicit def basic[A <: Nat]: Add[_0, A, A] = new +[_0, A] {
type Result = A
}
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: +[A, B]): Add[Succ[A], B, Succ[evidence.Result]] =
new +[A, B] {
type Out = Succ[evidence.Result]
}
No, but _2 = Succ[_1], so maybe I can look further

Is there an implicit value of type +[_1, _3]?
implicit def basic[A <: Nat]: Add[_0, A, A] = new +[_0, A] {
type Result = A
}
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: +[A, B]): Add[Succ[A], B, Succ[evidence.Result]] =
new +[A, B] {
type Out = Succ[evidence.Result]
}
No, but _1 = Succ[_0], so maybe I can look further

Is there an implicit value of type +[_0, _3]?
implicit def basic[A <: Nat]: Add[_0, A, A] = new +[_0, A] {
type Result = A
}
Yes, and its result is _3!
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: +[A, B]): Add[Succ[A], B, Succ[evidence.Result]] =
new +[A, B] {
type Out = Succ[evidence.Result]
}

So I can also build an implicit value of type +[Succ[_0], _3]:
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: +[A, B]): Add[Succ[A], B, Succ[evidence.Result]] =
new +[A, B] {
type Out = Succ[evidence.Result]
}
And its result is Succ[_3]
I have an evidence of type +[_0, _3]

So I can also build an implicit value of type +[Succ[_1], _3]:
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: +[A, B]): Add[Succ[A], B, Succ[evidence.Result]] =
new +[A, B] {
type Out = Succ[evidence.Result]
}
And its result is Succ[Succ[_3]]
So I can build an implicit of type +[_2, _3] and its result is Succ[Succ[_3]]
show(+[_2, _3])
I have an evidence of type +[_1, _3]

17. Ep 4: Subtracting Numbers
trait -[A <: Nat, B <: Nat] {
type Result <: Nat
}
type Subtract[A <: Nat, B <: Nat, R <: Nat] = -[A, B] {
type Result = R
}
Read: if someone gives me an instance of Subtract, I’ll consider it a - and I’ll store the type member
Read: the type of a subtraction of two numbers with the result stored as a type member
(we’ll need this later)

18. Axiom 1: subtracting 0 gives back the same number
Read: for every number, there is an instance of a subtract operation with that number and zero
implicit def basic[A <: Nat]: Subtract[A, _0, A] = new -[A, _0] {
type Result = A
}
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: -[A, B])
: Subtract[Succ[A], Succ[B], evidence.Result] =
new +[Succ[A], B] {
type Out = Succ[evidence.Result]
}
Axiom 2: subtracting two random numbers
Read: for every two ”numbers” A and B,
if there is an instance of a subtract operation on A and B,
• then there is an instance of a subtract operation on Succ[A] and Succ[B] where
• the result is the same as the subtraction of A and B
Ep 4: Subtracting Numbers

19. Bringing the evidence to the surface
(so we can see it clearly when we print its type tag)
def apply[A <: Nat, B <: Nat](implicit evidence: -[A, B]): new -[A, B] {
type Result = evidence.Result
}
show(-[_5, _2])
TypeTag[... {
type Result = Succ[Succ[Succ[_0]]]
}]
Ep 4: Subtracting Numbers

20. implicit def inductive[A <: Nat, B <: Nat](implicit evidence: -[A, B])
: Subtract[Succ[A], Succ[B], evidence.Result] =
new +[Succ[A], B] {
type Out = Succ[evidence.Result]
}
implicit def basic[A <: Nat]: Subtract[A, _0, A] = new -[A, _0] {
type Result = A
}
But how?
Is there an implicit value of type -[_5, _2]?
show(-[_5, _2])
No, but _2 = Succ[_1] and _5 = Succ[_4], so maybe I can look further
Ep 4: Subtracting Numbers

21. implicit def inductive[A <: Nat, B <: Nat](implicit evidence: -[A, B])
: Subtract[Succ[A], Succ[B], evidence.Result] =
new +[Succ[A], B] {
type Out = Succ[evidence.Result]
}
implicit def basic[A <: Nat]: Subtract[A, _0, A] = new -[A, _0] {
type Result = A
}
Is there an implicit value of type -[_4, _1]?
No, but _4 = Succ[_3] and _1 = Succ[_0], so maybe I can look further
Ep 4: Subtracting Numbers

22. implicit def inductive[A <: Nat, B <: Nat](implicit evidence: -[A, B])
: Subtract[Succ[A], Succ[B], evidence.Result] =
new +[Succ[A], B] {
type Out = Succ[evidence.Result]
}
implicit def basic[A <: Nat]: Subtract[A, _0, A] = new -[A, _0] {
type Result = A
}
Is there an implicit value of type -[_3, _0]?
Yes, and its result is _3!
Ep 4: Subtracting Numbers

23. implicit def inductive[A <: Nat, B <: Nat](implicit evidence: -[A, B])
: Subtract[Succ[A], Succ[B], evidence.Result] =
new +[Succ[A], B] {
type Out = Succ[evidence.Result]
}
So I can also build an implicit value of type -[Succ[_3], Succ[_0]]:
And its result is still _3
I have an evidence of type -[_3, _0]
Ep 4: Subtracting Numbers

24. So I can also build an implicit value of type -[Succ[_4], Succ[_1]]:
And its result is still _3
I have an evidence of type -[_4, _1]
implicit def inductive[A <: Nat, B <: Nat](implicit evidence: -[A, B])
: Subtract[Succ[A], Succ[B], evidence.Result] =
new +[Succ[A], B] {
type Out = Succ[evidence.Result]
}
So I can build an implicit of type -[_5, _2] and its result is _3
show(-[_5, _2])
Ep 4: Subtracting Numbers
show(-[_2, _5])
Error: no implicit arguments found

25. All hail the Scala compiler!

26. Scala rocks
Daniel Ciocîrlan
rockthejvm.com