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

Sealing in the flavor

Sealing in the flavor

Kotlin brings sealed classes and functional programming paradigms to the table. A type safe design can do miracles for your codebase so let's take a look how we can model runtime errors in our type system and what advantages FP concepts offer.

Simon Vergauwen

August 24, 2017
Tweet

More Decks by Simon Vergauwen

Other Decks in Programming

Transcript

  1. SEALING IN THE FLAVOR

    View full-size slide

  2. SIMON VERGAUWEN

    View full-size slide

  3. SEALING IN THE FLAVOUR
    FUNCTIONAL PROGRAMMING IS SCARY
    ▸ Monoid
    ▸ Monad
    ▸ Functor
    ▸ Catamorphism
    ▸ ….

    View full-size slide

  4. SEALING IN THE FLAVOUR
    FUNCTIONAL PROGRAMMING IS SCARY UNFAMILIAR
    ▸ Monoid
    ▸ Monad
    ▸ Functor
    ▸ Catamorphism
    ▸ ….

    View full-size slide

  5. SEALING IN THE FLAVOUR
    FUNCTIONAL PROGRAMMING IS SCARY UNFAMILIAR
    ▸ Monoid
    ▸ Monad
    ▸ Functor
    ▸ Catamorphism
    ▸ ….
    ▸ Chainable

    View full-size slide

  6. SEALING IN THE FLAVOUR
    FUNCTIONAL PROGRAMMING IS SCARY UNFAMILIAR
    ▸ Monoid
    ▸ Monad
    ▸ Functor
    ▸ Catamorphism
    ▸ ….
    ▸ Chainable
    ▸ Aggregatable

    View full-size slide

  7. SEALING IN THE FLAVOUR
    FUNCTIONAL PROGRAMMING IS SCARY UNFAMILIAR
    ▸ Monoid
    ▸ Monad
    ▸ Functor
    ▸ Catamorphism
    ▸ ….
    ▸ Chainable
    ▸ Aggregatable
    ▸ Mappable

    View full-size slide

  8. SEALING IN THE FLAVOUR
    FUNCTIONAL PROGRAMMING IS SCARY UNFAMILIAR
    ▸ Monoid
    ▸ Monad
    ▸ Functor
    ▸ Catamorphism
    ▸ ….
    ▸ Chainable
    ▸ Aggregatable
    ▸ Mappable
    ▸ Collapsable

    View full-size slide

  9. SEALING IN THE FLAVOUR
    WHAT ARE SEALED CLASSES?
    ▸ Tagged Union, Sum type
    “A data structure used to hold a value that could take several
    different, but fixed, types. Only one of the types can be in use at any
    one time.

    View full-size slide

  10. SEALING IN THE FLAVOUR
    MODELLING DATA
    data class Visitor(
    val id: String?,
    val name: String?,
    val lastName: String?,
    val email: String?,
    val isEmailVerified: Boolean,
    val isAnonymous: Boolean
    )

    View full-size slide

  11. SEALING IN THE FLAVOUR
    MODELLING DATA
    data class Visitor(
    val id: String?,
    val name: String?,
    val lastName: String?,
    val email: String?,
    val isEmailVerified: Boolean,
    val isAnonymous: Boolean
    )
    DOMAIN LOGIC!?

    View full-size slide

  12. SEALING IN THE FLAVOUR
    MODELLING DATA
    data class Visitor(
    val id: String?,
    val name: String?,
    val lastName: String?,
    val email: String?,
    val isEmailVerified: Boolean,
    val isAnonymous: Boolean
    )
    DOMAIN LOGIC!?
    WHEN CAN WHAT FIELD BE NULL? BOOLEAN FLAGS?

    View full-size slide

  13. SEALING IN THE FLAVOUR
    MODELLING DATA
    abstract class Visitor {
    data class VerifiedUser(
    val id: String,
    val name: String,
    val lastName: String,
    val email: String
    ): Visitor()
    }

    View full-size slide

  14. SEALING IN THE FLAVOUR
    MODELLING DATA
    abstract class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(
    val name: String,
    val lastName: String,
    val email: String
    ): Visitor()
    }

    View full-size slide

  15. SEALING IN THE FLAVOUR
    MODELLING DATA
    abstract class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(...): Visitor()
    object Anonymous : Visitor()
    }

    View full-size slide

  16. SEALING IN THE FLAVOUR
    MODELLING DATA
    abstract class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(...): Visitor()
    object Anonymous : Visitor()
    }
    CLEAR MODEL OF OUR DOMAIN AND FIELDS

    View full-size slide

  17. SEALING IN THE FLAVOUR
    MODELLING DATA
    //com.github.nomisRev.Visitor.kt
    abstract class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(...): Visitor()
    object Anonymous : Visitor()
    }
    //some.hackery.HackyUser.kt
    data class HackyUser(val hackedId: String): Visitor()
    MIGHT BREAK MY LOGIC

    View full-size slide

  18. SEALING IN THE FLAVOUR
    MODELLING DATA
    //com.github.nomisRev.Visitor.kt
    sealed class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(...): Visitor()
    object Anonymous : Visitor()
    }
    //some.hackery.HackyUser.kt
    data class HackyUser(val hackedId: String): Visitor()
    SEALED (CLOSED) INHERITANCE

    View full-size slide

  19. SEALING IN THE FLAVOUR
    MODELLING DATA
    //com.github.nomisRev.Visitor.kt
    sealed class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(...): Visitor()
    object Anonymous : Visitor()
    }
    //some.hackery.HackyUser.kt
    data class HackyUser(val hackedId: String): Visitor()
    SEALED (CLOSED) INHERITANCE

    View full-size slide

  20. SEALING IN THE FLAVOUR
    MODELLING DATA
    sealed class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(...): Visitor()
    object Anonymous : Visitor()
    }
    fun sayHello(visitor: Visitor): String = when (visitor) {
    is Visitor.VerifiedUser -> "Hello ${visitor.name}," +
    "here are you settings/${visitor.id}"
    is Visitor.UnverifiedUser -> "Hello ${visitor.name}," +
    "we have sent a verification email to ${visitor.email}"
    is Visitor.Anonymous -> "Hello, would you like to register?"
    }

    View full-size slide

  21. SEALING IN THE FLAVOUR
    MODELLING DATA
    sealed class Visitor {
    data class VerifiedUser(...): Visitor()
    data class UnverifiedUser(...): Visitor()
    object Anonymous : Visitor()
    }
    fun sayHello(visitor: Visitor): String = when (visitor) {
    is Visitor.VerifiedUser -> "Hello ${visitor.name}," +
    "here are you settings/${visitor.id}"
    is Visitor.UnverifiedUser -> "Hello ${visitor.name}," +
    "we have sent a verification email to ${visitor.email}"
    is Visitor.Anonymous -> "Hello, would you like to register?"
    }
    SMART CASTING

    View full-size slide

  22. SEALING IN THE FLAVOUR
    SEALED CLASS VS ABSTRACT CLASS
    ▸ Compiler knows all subtypes
    ▸ No inheritance hierarchy - type definition
    ▸ Strong guarantees about types

    View full-size slide

  23. SEALING IN THE FLAVOUR
    EXAMPLE
    sealed class MathExpression {
    data class Add(
    val left: MathExpression,
    val right: MathExpression
    ) : MathExpression()
    data class Subtract(
    val left: MathExpression,
    val right: MathExpression
    ) : MathExpression()
    data class Number(val value: Int) : MathExpression()
    }

    View full-size slide

  24. SEALING IN THE FLAVOUR
    EXAMPLE
    sealed class MathExpression {
    data class Add(left, right) : MathExpression()
    data class Subtract(left, right) : MathExpression()
    data class Number(val value: Int) : MathExpression()
    fun eval(): Int = when (this) {
    }
    }
    Error:(9,23) Kotlin:’when’ expression must be exhaustive,

    add `is Add`, `is Subtract` and `is Number` branches

    or `else` branch instead.

    View full-size slide

  25. SEALING IN THE FLAVOUR
    EXAMPLE
    sealed class MathExpression {
    data class Add(left, right) : MathExpression()
    data class Subtract(left, right) : MathExpression()
    data class Number(val value: Int) : MathExpression()
    fun eval(): Int = when (this) {
    is MathExpression.Add -> left.eval() + right.eval()
    is MathExpression.Subtract -> left.eval() - right.eval()
    is MathExpression.Number -> value
    }
    }

    View full-size slide

  26. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP

    View full-size slide

  27. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP
    sealed class OOPMathExpression {
    abstract fun eval(): Int
    data class Add(left, right) : OOPMathExpression() {
    override fun eval() = left.eval() + right.eval()
    }
    data class Subtract(left, right) : OOPMathExpression() {
    override fun eval() = left.eval() - right.eval()
    }
    data class Number(value) : OOPMathExpression() {
    override fun eval() = value
    }
    }

    View full-size slide

  28. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP
    sealed class OOPMathExpression {
    abstract fun eval(): Int
    data class Add(left, right) : OOPMathExpression() {
    override fun eval() = left.eval() + right.eval()
    }
    data class Subtract(left, right) : OOPMathExpression() {
    override fun eval() = left.eval() - right.eval()
    }
    data class Number(value) : OOPMathExpression() {
    override fun eval() = value
    }
    data class Division(left, right) : OOPMathExpression()
    }
    COMPILER DEMANDS IMPLEMENTATION

    View full-size slide

  29. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP
    sealed class OOPMathExpression {
    abstract fun eval(): Int
    data class Add(left, right) : OOPMathExpression() {
    override fun eval() = left.eval() + right.eval()
    }
    data class Subtract(left, right) : OOPMathExpression() {
    override fun eval() = left.eval() - right.eval()
    }
    data class Number(value) : OOPMathExpression() {
    override fun eval() = value
    }
    data class Division(left, right) : OOPMathExpression() {
    override fun eval() = left.eval() / right.eval()
    }
    }

    View full-size slide

  30. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP
    sealed class MathExpression {
    data class Add(left, right) : MathExpression()
    data class Subtract(left, right) : MathExpression()
    data class Number(value) : MathExpression()
    data class Division(left, right): MathExpression()
    fun eval(): Int = when (this) {
    is MathExpression.Add -> left.eval() + right.eval()
    is MathExpression.Subtract -> left.eval() - right.eval()
    is MathExpression.Number -> value
    is MathExpression.Add -> left.eval() / right.eval()
    }
    }

    View full-size slide

  31. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP
    sealed class MathExpression {
    data class Add(left, right) : MathExpression()
    data class Subtract(left, right) : MathExpression()
    data class Number(value) : MathExpression()
    data class Division(left, right): MathExpression()
    fun eval(): Int = when (this) {
    is MathExpression.Add -> left.eval() + right.eval()
    is MathExpression.Subtract -> left.eval() - right.eval()
    is MathExpression.Number -> value
    is MathExpression.Add -> left.eval() / right.eval()
    }
    }
    ANY ISSUES HERE???

    View full-size slide

  32. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP
    sealed class MathExpression {
    data class Add(left, right) : MathExpression()
    data class Subtract(left, right) : MathExpression()
    data class Number(value) : MathExpression()
    data class Division(left, right): MathExpression()
    fun eval(): Int = when (this) {
    is MathExpression.Add -> left.eval() + right.eval()
    is MathExpression.Subtract -> left.eval() - right.eval()
    is MathExpression.Number -> value
    is MathExpression.Add -> left.eval() / right.eval()
    }
    }
    ANY ISSUES HERE???
    JAVA.LANG.ARITHMETICEXCEPTION: / BY ZERO
    AT MATHEXPRESSION2.EVAL(MATHEXPRESSION2.KT:13)

    View full-size slide

  33. SEALING IN THE FLAVOUR
    LET’S ADD OPERATION. OOP VS FP
    Add new method Add new data
    Polymorpish
    Change existing
    code
    Existing code
    unchanged
    Pattern
    matching
    Existing code
    unchanged
    Change existing
    code

    View full-size slide

  34. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    sealed class MathResult {
    data class Success(val value: Int) : MathResult()
    data class Failure(val exception: Throwable) : MathResult()
    }

    View full-size slide

  35. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    sealed class MathExpression {
    data class Add(left, right) : MathExpression()
    data class Number(value: Int) : MathExpression()
    data class Division(left, right) : MathExpression()
    fun eval(): MathResult = when (this) { ... }
    }

    View full-size slide

  36. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Number -> MathResult.Success(value)
    }

    View full-size slide

  37. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Add -> {
    val lR: MathResult = left.eval()
    }
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  38. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Add -> {
    val lR: MathResult = left.eval()
    when (lR) {
    is Failure -> lR
    is Success -> { }
    }
    }
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  39. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Add -> {
    val lResult: MathResult = left.eval()
    when (lResult) {
    is Failure -> lResult
    is Success -> {
    val rR: MathResult = right.eval()
    }
    }
    }
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  40. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Add -> {
    val lResult: MathResult = left.eval()
    when (lResult) {
    is Failure -> lResult
    is Success -> {
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lR.value + rR.value)
    }
    }
    }
    }
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  41. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Division -> {
    val lR: MathResult = left.eval()
    }
    is MathExpression.Add -> {…}
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  42. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Division -> {
    val lR = left.eval()
    when (lR) {
    is Failure -> lR
    is Success -> { }
    }
    is MathExpression.Add -> {…}
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  43. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Division -> {
    val lR = left.eval()
    when (lR) {
    is Failure -> lR
    is Success -> {
    val rR = right.eval()
    }
    }
    is MathExpression.Add -> {…}
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  44. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Division -> {
    val lR = left.eval()
    when (lR) {
    is Failure -> lR
    is Success -> {
    val rR = right.eval()
    when (rR) {
    is Failure -> rR
    is Success ->
    if (rR.value != 0) Success(lR.value / rR.value)
    else Failure(ArithmeticException(“Cannot / by zero"))
    }
    }
    }
    }
    is MathExpression.Add -> {…}
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  45. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Division ->
    if (rR.value != 0) Success(lR.value / rR.value)
    else Failure(ArithmeticException(“Cannot / by zero"))
    is MathExpression.Add -> Success(lR.value + rR.value)
    is MathExpression.Number -> Success(value)
    }

    View full-size slide

  46. SEALING IN THE FLAVOUR
    HANDLE EXCEPTIONS
    fun eval(): MathResult = when (this) {
    is MathExpression.Division ->
    if (rR.value != 0) lR.value / rR.value
    else ArithmeticException(“Cannot / by zero”)
    is MathExpression.Add -> lR.value + rR.value
    is MathExpression.Number -> value
    }

    View full-size slide

  47. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    val lResult: MathResult = left.eval()
    when (lResult) {
    is Failure -> lResult
    is Success -> {
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lR.value + rR.value)
    }
    }
    }
    }

    View full-size slide

  48. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    val lResult: MathResult = left.eval()
    when (lResult) {
    is Failure -> lResult
    is Success -> {
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lR.value + rR.value)
    }
    }
    }
    }
    IF FAILURE IMMEDIATELY RETURN
    ELSE COMPUTE NEW MATH RESULT

    View full-size slide

  49. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    val lResult: MathResult = left.eval()
    when (lResult) {
    is Failure -> lResult
    is Success -> {
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lR.value + rR.value)
    }
    }
    }
    }
    IF FAILURE IMMEDIATELY RETURN
    ELSE COMPUTE NEW MATH RESULT
    fun flatMap(f: (Int) -> MathResult): MathResult = when (this) {
    is MathResult.Failure -> this
    is MathResult.Success -> f(value)
    }

    View full-size slide

  50. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    left.eval().flatMap { lValue ->
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lValue + rR.value)
    }
    }
    }

    View full-size slide

  51. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    left.eval().flatMap { lValue ->
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lValue + rR.value)
    }
    }
    }
    IF FAILURE IMMEDIATELY RETURN
    ELSE COMPUTE NEW VALUE

    View full-size slide

  52. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    left.eval().flatMap { lValue ->
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lValue + rR.value)
    }
    }
    }
    IF FAILURE IMMEDIATELY RETURN
    ELSE COMPUTE NEW VALUE
    fun map(f: (Int) -> Int): MathResult = when(this) {
    is MathResult.Failure -> this
    is MathResult.Success -> MathResult.Success(f(value))
    }

    View full-size slide

  53. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    left.eval().flatMap { lValue ->
    right.eval().map { rValue ->
    lValue + rValue
    }
    }
    }

    View full-size slide

  54. SEALING IN THE FLAVOUR
    GO AWAY BOILERPLATE!
    is MathExpression.Add -> {
    val lResult: MathResult = left.eval()
    when (lResult) {
    is Failure -> lResult
    is Success -> {
    val rR: MathResult = right.eval()
    when (rR) {
    is Failure -> rResult
    is Success -> Success(lR.value + rR.value)
    }
    }
    }
    }
    is MathExpression.Add -> {
    left.eval().flatMap { lValue ->
    right.eval().map { rValue ->
    lValue + rValue
    }
    }
    }

    View full-size slide

  55. SEALING IN THE FLAVOUR
    HANDLING ERRORS - NULL IS BAD!
    ▸ Bypasses type system
    ▸ Abused to model non existing values.
    ▸ Nulls can appear anywhere, in any value or variable.
    ▸ No way to control them as they can be anything.
    ▸ Hard to debug and leads to bugs

    View full-size slide

  56. SEALING IN THE FLAVOUR
    HANDLING ERRORS - EXCEPTIONS ARE BAD!
    ▸ Bypasses type system
    ▸ Can appear anywhere (unchecked).
    ▸ Abused to model non existing values.
    ▸ Can be hard to debug and leads to bugs

    View full-size slide

  57. SEALING IN THE FLAVOUR
    SOLUTIONS

    View full-size slide

  58. SEALING IN THE FLAVOUR
    SOLUTIONS
    sealed class Option {
    data class Some(val value: T) : Option()
    object None : Option()
    }

    View full-size slide

  59. SEALING IN THE FLAVOUR
    SOLUTIONS
    sealed class Try {
    data class Success(val value: T) : Try()
    data class Failure(val exception: Throwable) : Try()
    }
    sealed class Option {
    data class Some(val value: T) : Option()
    object None : Option()
    }

    View full-size slide

  60. SEALING IN THE FLAVOUR
    SOLUTIONS
    sealed class Try {
    data class Success(val value: T) : Try()
    data class Failure(val exception: Throwable) : Try()
    }
    sealed class Option {
    data class Some(val value: T) : Option()
    object None : Option()
    }
    sealed class Either {
    data class Left(val left: T) : Either()
    data class Right(val right: R) : Either()
    }

    View full-size slide

  61. https://speakerdeck.com/nomisrev

    https://github.com/nomisRev
    Any questions?

    View full-size slide