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

A Number by Any Other Name

A Number by Any Other Name

In 1999, NASA's Mars Climate Orbiter crashed into the red planet as it was
trying to enter orbit. The problem? Two pieces of software were using different
units in their calculations.

If you've ever calculated a wrong value because you forgot to convert seconds
into hours or because you accidentally inverted two number arguments in a
function, then you've encountered this same bug.

Join me as we leverage Elm's type system to catch these bugs, get better
error messages, and improved readability in the process. Along the way we'll
examine some assumptions we have around numbers and the operations we can do on
them.

Watch the recorded talk: https://www.youtube.com/watch?v=WnTw0z7rD3E

Joël Quenneville

April 26, 2019
Tweet

More Decks by Joël Quenneville

Other Decks in Technology

Transcript

  1. [...] the navigation software algorithm therefore, underestimated the effect on

    the spacecraft trajectory by a factor of 4.45 [...]
  2. ARGUMENT ORDER ERROR speed : Float -> Float -> Float

    speed distance time = distance / time
  3. ARGUMENT ORDER ERROR distance = 10 time = 5 --

    WHICH ONE? speed distance time speed time distance
  4. COMPILER CAN'T TELL THE DIFFERENCE Float -> Float -> Float

    -- SAME AS Meter -> Second -> MeterPerSecond
  5. [the Elm compiler] has my back when I make a

    dumb mistake. It brings joy into front-end programming. - Joël Quenneville
  6. CUSTOM TYPES type Meter = Meter Float type Second =

    Second Float type MetersPerSecond = MetersPerSecond Float
  7. speed : Meter -> Second -> MetersPerSecond speed (Meter m)

    (Second s) = MetersPerSecond (m / s)
  8. ARGUMENT ORDER ERROR - TYPED distance = Meter 10 time

    = Second 5 -- Wrong argument order myVal = speed time distance
  9. The 1st argument to `speed` is not what I expect:

    21| myVal = speed time distance ^^^^ This `time` value is a: Second But `speed` needs the 1st argument to be: Meter
  10. UNIT CONVERSION ERROR minutes = 40 hours = 2 total

    = minutes + hours -- RETURNS 42 -- SHOULD RETURN 160 OR 2.67
  11. COMBINATORIAL EXPLOSION add : Minute -> Hour -> ??? add

    : Minute -> Minute -> ??? add : Hour -> Hour -> ???
  12. !

  13. OPAQUE TYPES module Time exposing (Time, fromMinutes) -- OTHER STUFF

    fromMinutes : Float -> Time fromMinutes mins = Time mins
  14. OPAQUE TYPES module Time exposing (Time, fromMinutes, fromHours) -- OTHER

    STUFF fromHours : Float -> Time fromHours hrs = Time (hrs * 60)
  15. OPAQUE TYPES module Time exposing (Time, fromMinutes, fromHours, add) --

    OTHER STUFF add : Time -> Time -> Time add (Time t1) (Time t2) = Time (t1 + t2)
  16. OPAQUE TYPES module Time exposing (Time, fromMinutes, fromHours, add) --

    OTHER STUFF inHours : Time -> Float inHours (Time t) toFloat t / 60
  17. minutes = Time.fromMinutes 40 hours = Time.fromHours 2 total =

    Time.add hours minutes -- RETURNS `Time 160`
  18. MONEY usd = 5 eur = 10 total = usd

    + eur -- RETURNS 15 -- SHOULD BE ??
  19. RULES 1. Can only add currencies of the same type

    2. Can convert currencies of different types given a rate
  20. module Usd exposing(Usd, fromCents, add, toEur) add : Usd ->

    Usd -> Usd toEur : Float -> Usd -> Eur -- MORE CODE
  21. module Eur exposing (Eur, fromCents, add, toUsd) add : Eur

    -> Eur -> Eur toUsd : Float -> Eur -> Usd -- MORE CODE
  22. SUCCESS? usd = Usd.fromCents 500 eur = Eur.fromCents 1000 total

    = eur |> Eur.toUsd 1.14 |> Usd.add usd -- RETURNS `Usd 1640`
  23. !

  24. RULES 1. Can only add currencies of the same type

    2. Can convert currencies of different types given a rate
  25. add : Currency a -> Currency a -> Currency a

    convert : ConversionRate from to -> Currency from -> Currency to
  26. module Currency exposing (Currency, Usd, Eur, Jpy) type Usd =

    Usd type Eur = Eur type Jpy = Jpy type Currency a = Currency Float
  27. module Currency exposing (Currency, Usd, Eur, Jpy, add) -- OTHER

    CODE add : Currency a -> Currency a -> Currency a add (Currency c1) (Currency c2) = Currency (c1 + c2)
  28. usd : Currency Usd usd = Currency 500 eur :

    Currency Eur eur = Currency 1000 total = Currency.add usd eur
  29. The 2nd argument to `add` is not what I expect:

    29| add usd eur ^^^ This `eur` value is a: Currency Eur But `add` needs the 2nd argument to be: Currency Usd
  30. CONVERSION - PHANTOM TYPE X 2 type ConversionRate from to

    = ConversionRate Float convert : ConversionRate from to -> Currency from -> Currency to convert (ConversionRate rate) (Currency c) = Currency (rate * c)
  31. -- usd AND eur DEFINITIONS HIDDEN rate : ConversionRate Eur

    Usd rate = ConversionRate 1.12 total = eur |> Currency.convert rate |> Currency.add usd
  32. CUSTOM TYPES type alias Point = (Int, Int) type WorldSpace

    = WorldSpace Point type ScreenSpace = ScreenSpace Point
  33. CONVERTING BETWEEN THE TWO - VIEWPORT type alias Viewport =

    { position : WorldSpace , width : GameDistance , height : GameDistance }
  34. renderCharacter : Viewport -> Character -> Svg a renderCharacter vp

    character = character.position |> toScreenCoords vp |> renderSpriteAt "character.png"
  35. F#