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

Sealed classes opened my mind

Sealed classes opened my mind

How we use Kotlin to tame state

Patrick Cousins

October 05, 2018
Tweet

More Decks by Patrick Cousins

Other Decks in Programming

Transcript

  1. Patrick Cousins @alostpacket Sealed classes opened my mind How we

    use Kotlin to tame state at Etsy a ❤ story
  2. 4 Listings tags attributes materials variations production partners shipping profiles

    payment methods conversations messages images lions tigers snippets
  3. 6 Listings tags attributes materials variations production partners shipping profiles

    payment methods conversations messages images lions tigers snippets
  4. 8 Listings tags attributes materials variations production partners shipping profiles

    payment methods conversations messages images lions tigers snippets
  5. 13 MVVM + sealed classes =  Redux + sealed

    classes =  MVI + sealed classes =  Repository + sealed classes = 
  6. 15 Sealed classes are used for representing restricted class hierarchies,

    when a value can have one of the types from a limited set, but cannot have any other type. They are, in a sense, an extension of enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances which can contain state. Definition of sealed classes from the official documentation
  7. 20 sealed class Result { class Success(val items: List<String>): Result()

    class Failure(val error: Throwable): Result() }
  8. 24 when (result) { is Result.Success -> showItems(result.items) is Result.Failure

    -> result.error.printStackTrace() } Look Ma! No else!
  9. 25 sealed class Result { class Success(val items: List<String>): Result()

    class Failure(val error: Throwable): Result() object Cancelled: Result() }
  10. 26 val done = when (result) { is Result.Success ->

    showList(result.items) is Result.Failure -> result.error.printStackTrace() }
  11. 27 val done = when (result) { is Result.Success ->

    showList(result.items) is Result.Failure -> result.error.printStackTrace() }
  12. 28 val done = when (result) { is Result.Success ->

    showList(result.items) is Result.Failure -> result.error.printStackTrace() }
  13. 29 val done = when (result) { is Result.Success ->

    showList(result.items) is Result.Failure -> result.error.printStackTrace() }
  14. 30 val done = when (result) { is Result.Success ->

    showList(result.items) is Result.Failure -> result.error.printStackTrace() is Result.Cancelled -> TODO() }
  15. 31 sealed class Result { class Success(val items: List<String>): Result()

    class Failure(val error: Throwable): Result() object Cancelled: Result() } val done = when (result) { is Result.Success -> showList(result.items) is Result.Failure -> result.error.printStackTrace() is Result.Cancelled -> hideStuff() }
  16. 33 sealed class Yarn { class Cotton : Yarn() open

    class Wool : Yarn() } class Merino : Yarn.Wool()
  17. 34 val yarn = when (spool) { is Yarn.Wool ->

    knit() is Yarn.Cotton -> knit() is Yarn.Wool.Merino -> ??? }
  18. 35 val yarn = when (spool) { is Yarn.Wool ->

    knit() is Yarn.Cotton -> knit() }
  19. 36 sealed class Yarn { class Cotton : Yarn() class

    Silk : Yarn() sealed class Wool : Yarn() { class Merino : Wool() class Alpaca : Wool() } }
  20. 37 val yarn = when (spool) { is Yarn.Silk ->

    knit() is Yarn.Cotton -> knit() }
  21. 38 val yarn = when (spool) { is Yarn.Silk ->

    knit() is Yarn.Cotton -> knit() is Yarn.Wool.Merino -> TODO() is Yarn.Wool.Alpaca -> TODO() }
  22. 39 val yarn = when (spool) { is Yarn.Silk ->

    knit() is Yarn.Cotton -> knit() is Yarn.Wool -> knit() }
  23. 40 //same file only! sealed class Yarn { class Cotton

    : Yarn() class Silk : Yarn() } sealed class Wool : Yarn() { class Merino : Wool() class Alpaca : Wool() }
  24. 41 val yarn = when (spool) { is Yarn.Silk ->

    knit() is Yarn.Cotton -> knit() }
  25. 42 val yarn = when (spool) { is Yarn.Silk ->

    knit() is Yarn.Cotton -> knit() is Wool.Merino -> TODO() is Wool.Alpaca -> TODO() }
  26. 43 val yarn = when (spool) { is Yarn.Silk ->

    knit() is Yarn.Cotton -> knit() is Wool -> //NOPE doesn’t work now }
  27. Small Alpaca and Wool Socks, Grown in Michigan By NorthStarAlpacas

    As our state grows in complexity, there are times in which we may only be concerned about a subset of that state. Only wool socks please
  28. 47 val warmWool = yarnStash.filterIsInstance<Wool>() warmWool.forEach { val yarn =

    when (it) { is Wool.Merino -> knit() is Wool.Alpaca -> knit() } }
  29. 48 //Using Rx and Redux yarn.asObservable() .map { it.wool }

    .subscribe { val yarn = when (it) { is Wool.Merino -> knit() is Wool.Alpaca -> knit() } }
  30. 49 //Using Rx and Redux yarn.asObservable() .map { it.wool }

    .distinctUntilChanged() .subscribe { val yarn = when (it) { is Wool.Merino -> knit() is Wool.Alpaca -> knit() } }
  31. 50 //Using Rx and Redux yarn.asObservable() .map { it.wool }

    .distinctUntilChanged() .subscribe { val yarn = when (it) { is Wool.Merino -> knit() is Wool.Alpaca -> knit() } }
  32. 51 //Using Rx and Redux yarn.asObservable() .map { it.wool }

    .distinctUntilChanged { wool -> wool.images } .subscribe { val yarn = when (it) { is Wool.Merino -> knit() is Wool.Alpaca -> knit() } }
  33. 52 //Using Rx and Redux yarn.asObservable() .map { it.wool }

    .distinctUntilChanged { old, new -> new.num > 0 } .subscribe { val yarn = when (it) { is Wool.Merino -> knit() is Wool.Alpaca -> knit() } }
  34. 55 Social Sign On Facebook Google Username Email Password Two

    Factor Auth Smoke Signal Carrier Pigeon Morse Code Interpretive dance
  35. 56 • Async calls to an External API • Users

    leaving the app • Pausing and waiting for input • Stateful flows
  36. 57

  37. 59 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMessage: String) : SocialSignInResult() }
  38. 60 sealed class SignInResult { data class Success(val token: String)

    : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMessage: String) : SignInResult() }
  39. 61 sealed class TwoFAResult { object Success : TwoFAResult() data

    class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  40. 62 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  41. 63 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  42. 64 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  43. 65 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  44. 67 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  45. 68 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  46. 69 Social Sign in -> Sign in -> Error Got

    an error at any point along the way !=
  47. 70 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  48. 73 sealed class SocialSignInResult { data class Success(val token: String)

    : SocialSignInResult() data class Link(val token: String) : SocialSignInResult() data class Register(val token: String) : SocialSignInResult() data class Error(val errorMsg: String) : SocialSignInResult() } sealed class SignInResult { data class Success(val token: String) : SignInResult() data class TwoFactorAuth(val token: String) : SignInResult() data class Fail(val token: String) : SignInResult() data class RetrySocial(val errorMsg: String) : SignInResult() } sealed class TwoFAResult { object Success : TwoFAResult() data class Retry2FA(val token: String) : TwoFAResult() data class Fail(val errorMsg: String) : TwoFAResult() data class RetrySocial(val errorMsg: String) : TwoFAResult() }
  49. 75 sealed class SocialSignIn { sealed class SignIn: SocialSignIn() {

    class Success : SignIn() sealed class TwoFactor : SignIn() { class Success : TwoFactor() class RetryTwoFactor : TwoFactor() class Fail : TwoFactor() class RetrySocial : TwoFactor() } class Fail : SignIn() class RetrySocial : SignIn() } sealed class Link: SocialSignIn() { class Success : Link() sealed class TwoFactor : Link() { class Success : TwoFactor() class RetryTwoFactor : TwoFactor() class Fail : TwoFactor() class RetrySocial : TwoFactor() } class Fail : Link() class RetrySocial : Link() } sealed class Register : SocialSignIn() { class Success : Register() class Fail : Register() class RetrySocial : Register() } class Error(val errorMessage: String) : SocialSignIn() }
  50. 77 sealed class SocialSignIn { class Error(val errorMessage: String) :

    SocialSignIn() } sealed class Register : SocialSignIn() { class Success : Register() class Fail : Register() class RetrySocial : Register() } sealed class SignIn: SocialSignIn() { class Success : SignIn() class Fail : SignIn() class RetrySocial : SignIn() } sealed class TwoFactor : SignIn() { class Success : TwoFactor() class RetryTwoFactor : TwoFactor() class Fail : TwoFactor() class RetrySocial : TwoFactor() } sealed class Link: SocialSignIn() { class Success : Link() class Fail : Link() class RetrySocial : Link() }
  51. 80 If we can rely on contracts and preconditions, we

    can do more Compartmentalize the unknown
  52. 83 We stand on the shoulders of giants” -A pragmatic

    person who likes cheesy quotes “
  53. 85 Visitor pattern Great: Type safety Works around Java’s lack

    of dynamic dispatch Polymorphic can keep things loosely coupled
  54. 86 Visitor pattern Not so great: Lots to keep track

    of Lots of indirection Not fluent / symantec Not friendly to beginners
  55. 87 interface CarElement { void accept(CarElementVisitor visitor); } interface CarElementVisitor

    { void visit(Body body); void visit(Car car); void visit(Engine engine); void visit(Wheel wheel); } class Car implements CarElement { CarElement[] elements; public Car() { this.elements = new CarElement[] { new Wheel("front left"), new Wheel("front right"), new Wheel("back left"), new Wheel("back right"), new Body(), new Engine() }; } public void accept(final CarElementVisitor visitor) { for (CarElement elem : elements) { elem.accept(visitor); } visitor.visit(this); } } class Body implements CarElement { public void accept(final CarElementVisitor visitor) { visitor.visit(this); } } class Engine implements CarElement { public void accept(final CarElementVisitor visitor) { visitor.visit(this); } } class Wheel implements CarElement { private String name; public Wheel(final String name) { this.name = name; } public String getName() { return name; } public void accept(final CarElementVisitor visitor) { visitor.visit(this); } } class CarElementDoVisitor implements CarElementVisitor { public void visit(final Body body) { System.out.println("Moving my body"); } public void visit(final Car car) { System.out.println("Starting my car"); } public void visit(final Wheel wheel) { System.out.println("Kicking my " + wheel.getName() + " wheel"); } public void visit(final Engine engine) { System.out.println("Starting my engine"); } } class CarElementPrintVisitor implements CarElementVisitor { public void visit(final Body body) { System.out.println("Visiting body"); } public void visit(final Car car) { System.out.println("Visiting car"); } public void visit(final Engine engine) { System.out.println("Visiting engine"); } public void visit(final Wheel wheel) { System.out.println("Visiting " + wheel.getName() + " whee } } public class VisitorDemo { public static void main(final String[] args) { final Car car = new Car(); car.accept(new CarElementPrintVisitor()); car.accept(new CarElementDoVisitor()); } }
  56. 88 Great video on YouTube: The Visitor Design Pattern is

    obsolete in Kotlin By Fred Overflow https://www.youtube.com/watch?v=4qLj-kSOZLQ
  57. 90 Delegate Adapter pattern Reasonable type safety More symantec Not

    as hard for beginners Works around dynamic dispatch Polymorphic can keep things loosely coupled
  58. 91 Delegate Adapter pattern Lots to keep track of Lots

    of indirection Abstraction over an abstraction Leaky
  59. 92 We stand on the shoulders of giants” -A pragmatic

    person who likes cheesy quotes “
  60. 93 switch (...) { } Is instanceof a code smell?

    Should we prefer polymorphism? Why?
  61. 94 if (item instanceof Wheel) { //downcast and handle item

    as Wheel ((Wheel) item ).turn(); } else if (item instanceof Car) { //downcast and handle item as Car ((Car) item ).drive(); }
  62. 98 Final classes by default Sealed classes cannot be opened

    No need for polymorphism! Branches can only be defined in the same file But isn’t Kotlin’s is operator is the same as instanceof from Java!