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

Improving Code with Union Types in Elm

Improving Code with Union Types in Elm

Talk given at Boston Elm meetup May 2017.

A more polished version of this was turned into two blog posts:

- https://thoughtbot.com/blog/booleans-and-enums
- https://thoughtbot.com/blog/modeling-with-union-types

Avatar for Joël Quenneville

Joël Quenneville

May 11, 2017
Tweet

More Decks by Joël Quenneville

Other Decks in Technology

Transcript

  1. Commonly accepted attributes · Expressive · Easier to change ·

    Easier to read · More modular · More correct
  2. Sum type type Delivery = Delivery | Pickup | NoneSelected

    type alias Order = { id : Int , delivery : Delivery }
  3. Improvements · ✅ Easier to change · ✅ Easier to

    read · ❌ More modular · ❌ More correct
  4. Double Dependant Boolean type alias Order = { id :

    Int , delivery : Bool , thirdParty : Bool }
  5. What does this even mean { id = 1 ,

    delivery = False , thirdParty = True }
  6. Sum type type Delivery = Delivery | Pickup | ThirdPartyDelivery

    | NotSelected type alias Order = { id : Int , delivery : Delivery }
  7. Improvements · ✅ Easier to change · ✅ Easier to

    read · ❌ More modular · ❌ More correct
  8. Looks like type Mortality = Mortal | Immortal type Gender

    = Male | Female type Mobility = Stationary | Mobile type alias Character = { mortality : Mortality , gender : Gender , mobility : Mobility }
  9. Improvements · ✅ Easier to change · ✅ Easier to

    read · ❌ More modular · ❌ More correct
  10. Case statements move : Int -> Character -> Character move

    distance character = case character.mobility of "Mobile" -> { character | position = character.position + distance } "Stationary" -> character _ -> character
  11. Sum type gives error -- MISSING PATTERNS ------------------------------------------------------------ This `case`

    does not have branches for all possibilities. 11|> case character.mobility of 12|> Mobile -> 13|> { character | position = character.position + distance } 14|> 15|> Stationary -> 16|> character You need to account for the following values: Flying
  12. Sum type gives error -- NAMING ERROR ---------------------------------------------------------------- Cannot find

    variable `Mobil` 20| Character Mobil 55 ^^^^^ Maybe you want one of the following? Mobile
  13. Improvements · ✅ Easier to change · ✅ Easier to

    read · ❌ More modular · ✅ More correct
  14. Dangerous primitives type alias User = { name : String

    , age : Int , bankBalance : Int , salary : Int }
  15. Working with Cash addBalance : User -> Int -> User

    addBalance user amount = { user | bankBalance = bankBalance + amount }
  16. Custom type type Dollar = Dollar Int type alias User

    = { name : String , age : Int , bankBalance : Dollar , salary : Dollar }
  17. Using Dollar addBalance : User -> Dollar -> User addBalance

    user dollarAmount = let (Dollar balance) = user.bankBalance (Dollar amount) = dollarAmount in { user | bankBalance = Dollar (balance + amount) }
  18. Compiler error -- TYPE MISMATCH --------------------------------------------------------------- The 2nd argument to

    function `addBalance` is causing a mismatch. 21| addBalance user user.age ^^^^^^^^ Function `addBalance` is expecting the 2nd argument to be: Dollar But it is: Int
  19. Summing two Maybes sumMaybe : Maybe Int -> Maybe Int

    -> Maybe Int sumMaybe m1 m2 = Maybe.map2 (+) m1 m2
  20. Mapping to the rescue module Dollar exposing (Dollar, map2) map2

    : (Int -> Int) -> Dollar -> Dollar -> Dollar map2 function (Dollar d1) (Dollar d2) = Dollar (function d1 d2)
  21. Using Dollar addBalance : User -> Dollar -> Dollar addBalance

    user amount = { user | bankBalance = Dollar.map2 (+) user.bankBalance amount }
  22. Improvements · ✅ Easier to change · ✅ Easier to

    read · ❌ More modular · ✅ More correct
  23. Use in in Code path : List Point path =

    [ Point 1 1, , Point 100 100 ]
  24. Now you want to add 3D module Point exposing (Point)

    type alias Point = { x : Int, y : Int, z : Int}
  25. Wrapping in a type module Point exposing (Point, fromXY) type

    Point = Point { x : Int, y : Int } fromXY : (Int, Int) -> Point fromXY (x, y) = Point { x = x, y = y }
  26. Adding 3D module Point exposing (Point, fromXY) type Point =

    Point { x : Int, y : Int, z : Int } fromXY : (Int, Int) -> Point fromXY (x, y) = Point { x = x, y = y, z = 0 }
  27. Improvements · ✅ Easier to change · ✅ Easier to

    read · ✅ More modular · ❌ More correct
  28. Improvements · ✅ Easier to change · ✅ Easier to

    read · ❌ More modular · ✅ More correct · ✅ More expressive
  29. Explore the shape of data type RemoteData e a =

    NotAsked | Loading | Failure e | Success a
  30. Improvements · ✅ Easier to change · ✅ Easier to

    read · ❌ More modular · ❌ More correct · ✅ More expressive