Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Daniel Ciocîrlan - Type-Level Sorting in Scala

Shannon
March 28, 2019

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.

Shannon

March 28, 2019
Tweet

More Decks by Shannon

Other Decks in Programming

Transcript

  1. Rock the JVM!
    with Daniel Ciocîrlan

    View full-size slide

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

    View full-size slide

  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.

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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.

    View full-size slide

  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]

    View full-size slide

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

    View full-size slide

  9. Ep 3: Adding Numbers
    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)

    View full-size slide

  10. Ep 3: Adding Numbers
    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

    View full-size slide

  11. Ep 3: Adding Numbers
    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]]]]]
    }]
    Read: 5

    View full-size slide

  12. Ep 3: Adding Numbers
    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

    View full-size slide

  13. Ep 3: Adding Numbers
    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

    View full-size slide

  14. Ep 3: Adding Numbers
    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]
    }

    View full-size slide

  15. Ep 3: Adding Numbers
    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]

    View full-size slide

  16. Ep 3: Adding Numbers
    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]

    View full-size slide

  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)

    View full-size slide

  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

    View full-size slide

  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]]]
    }]
    Read: 3
    Ep 4: Subtracting Numbers

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  25. All hail the Scala compiler!

    View full-size slide

  26. Scala rocks
    Daniel Ciocîrlan
    rockthejvm.com

    View full-size slide