$30 off During Our Annual Pro Sale. View Details »

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 FLAVOUR FUNCTIONAL PROGRAMMING IS SCARY ▸ Monoid

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

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

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

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

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

    Monoid ▸ Monad ▸ Functor ▸ Catamorphism ▸ …. ▸ Chainable ▸ Aggregatable ▸ Mappable ▸ Collapsable
  7. 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.
  8. 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 )
  9. 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!?
  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 ) DOMAIN LOGIC!? WHEN CAN WHAT FIELD BE NULL? BOOLEAN FLAGS?
  11. 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() }
  12. 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() }
  13. SEALING IN THE FLAVOUR MODELLING DATA abstract class Visitor {

    data class VerifiedUser(...): Visitor() data class UnverifiedUser(...): Visitor() object Anonymous : Visitor() }
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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?" }
  19. 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
  20. SEALING IN THE FLAVOUR SEALED CLASS VS ABSTRACT CLASS ▸

    Compiler knows all subtypes ▸ No inheritance hierarchy - type definition ▸ Strong guarantees about types
  21. 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() }
  22. 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.
  23. 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 } }
  24. 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 } }
  25. 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
  26. 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() } }
  27. 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() } }
  28. 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???
  29. 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)
  30. 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
  31. SEALING IN THE FLAVOUR HANDLE EXCEPTIONS sealed class MathResult {

    data class Success(val value: Int) : MathResult() data class Failure(val exception: Throwable) : MathResult() }
  32. 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) { ... } }
  33. SEALING IN THE FLAVOUR HANDLE EXCEPTIONS fun eval(): MathResult =

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

    when (this) { is MathExpression.Add -> { val lR: MathResult = left.eval() } is MathExpression.Number -> Success(value) }
  35. 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) }
  36. 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) }
  37. 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) }
  38. 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) }
  39. 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) }
  40. 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) }
  41. 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) }
  42. 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) }
  43. 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 }
  44. 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) } } } }
  45. 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
  46. 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) }
  47. 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) } } }
  48. 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
  49. 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)) }
  50. SEALING IN THE FLAVOUR GO AWAY BOILERPLATE! is MathExpression.Add ->

    { left.eval().flatMap { lValue -> right.eval().map { rValue -> lValue + rValue } } }
  51. 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 } } }
  52. 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
  53. 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
  54. SEALING IN THE FLAVOUR SOLUTIONS sealed class Option<out T> {

    data class Some<out T>(val value: T) : Option<T>() object None : Option<Nothing>() }
  55. SEALING IN THE FLAVOUR SOLUTIONS sealed class Try<T> { data

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

    class Success<T>(val value: T) : Try<T>() data class Failure<T>(val exception: Throwable) : Try<T>() } sealed class Option<out T> { data class Some<out T>(val value: T) : Option<T>() object None : Option<Nothing>() } sealed class Either<out L, out R> { data class Left<out T>(val left: T) : Either<T, Nothing>() data class Right<out R>(val right: R) : Either<Nothing, R>() }