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

Safer Programming With Types

Safer Programming With Types

Swift London Lightning Talk.

A reminder that using well-defined types can result in safer programs.

Speaker notes included.

Abizer Nasir

October 25, 2016
Tweet

More Decks by Abizer Nasir

Other Decks in Programming

Transcript

  1. SAFER PROGRAMMING WITH TYPES
    Abizer Nasir ❦ @abizern ❦ abizern.org

    View Slide

  2. So - You want to write a function.
    LENGTH OF A
    CIRCULAR
    SEGMENT

    View Slide

  3. A function that returns the length of the arc produced by a
    radius sweeping out an angle.

    View Slide

  4. The formula is the length of the radius multiplied by the
    angle
    l = r . θ

    View Slide

  5. Hereʼs a simple way to write it...
    /// The length of arc produced by the `radius` sweeping `angle`.
    func arcLength(radius: Double, angle: Double) -> Double {
    return radius * angle
    }

    View Slide

  6. But when you test it, something doesnʼt seem right. The circumference should be
    around 6.3, but this is too large. - Oh, the angle should be in radians, not degrees.
    let arc = arcLength(radius: 1, angle: 45)
    // -> 45

    View Slide

  7. So you clarify the documentation and test it yourself.
    /// The length of arc produced by the `radius` sweeping `angle`
    /// where `angle` is in radians.
    func arcLength(radius: Double, angle: Double) -> Double {
    return radius * angle
    }
    let arc = arcLength(radius: 1, angle: .pi / 4)
    // -> 0.785

    View Slide

  8. All good.

    View Slide

  9. But it doesnʼt stop the user passing in degrees. I mean, who
    reads documentation?
    let arc = arcLength(radius: 1, angle: 45)
    // -> 45

    View Slide

  10. You could just say this is a user error, but youʼre an experienced developer
    and want to make such mistakes hard for a consumer of your work.
    !

    View Slide

  11. You can change the name of the parameter from angle to radians
    to be clearer. But it is still possible to pass in the wrong unit.
    /// The length of arc produced by the `radius` sweeping `radians`.
    func arcLength(radius: Double, radians: Double) -> Double {
    return radius * radians
    }

    View Slide

  12. You can use a typealias, but a typealias is a lie, it doesnʼt create a type,
    just a pseudonym for an existing type.
    You canʼt enforce a convention, there is nothing from stopping the
    consumer of your API from doing something wrong, even for something
    as simple as this.
    typealias Radians = Double
    /// The length of arc produced by the `radius` sweeping `angle`.
    func arcLength(radius: Double, angle: Radians) -> Double {
    return radius * angle
    }

    View Slide

  13. And they will...
    It is possible that the wrong unit will be passed and
    lead to a bug that will time to track down and fix.
    Donʼt even mention the agile process of raising a
    ticket, grooming, estimation, code review...
    let degrees = Double(45)
    let arc = arcLength(radius: 1, angle: degrees) // -> 45

    View Slide

  14. “Look at my face - and tell me I donʼt know what Iʼm talking
    about”
    !

    View Slide

  15. Using a strong type, something that represents “an angle” not just
    a number can mitigate against this.
    It restricts what can be provided in the parameter, and so the cases
    that have to be handled in a function, or method are also reduced.
    STRONG TYPING
    TO THE RESCUE

    View Slide

  16. There is new API introduced this year that provides strong types for
    dimensions and units. Using that we can write a more robust function:
    MEASUREMENTS

    WWDC 2016: 238 Measurements and Units

    View Slide

  17. Forgive the indentation, itʼs just to make it more visible on the slide.
    The radius has a length dimension, the angle has an “angle” dimension.
    Within the function we make sure that we are using the radian
    representation of whatever angle weʼve provided, and it returns a
    length. A proper, typed length, not just a number.
    /// The length arc produced by `radius` sweeping `angle`
    func arcLength(
    radius: Measurement,
    angle: Measurement
    ) -> Measurement {
    let radians = angle.converted(to: .radians).value
    return radius * radians
    }

    View Slide

  18. At the call site, the user can use any measurement, and still get
    the correct, typed, response.
    Of course, this isnʼt foolproof, you could still create the
    measurement with the UnitAngle.degrees, but it wonʼt let you
    make the mistake of reversing the parameters.
    The new Measurements API is really useful; invite me back
    sometime and Iʼll talk about it some more.
    let angle = Measurement(value: 45, unit: UnitAngle.degrees)
    let radius = Measurement(value: 1, unit: UnitLength.meters)
    let length = arcLength(radius: radius, angle: angle)
    // -> 0.785397882567309 m

    View Slide

  19. Youʼve heard this before, but this is the sort of thing being referred to.
    Sure, you could just pass in doubles, and fix the bugs as they come up.
    THE TYPE
    SYSTEM IS YOUR
    FRIEND

    View Slide

  20. Sorry about the indentation, itʼs just so it... No, letʼs do this
    right -
    SELF DOCUMENTING METHODS.
    /// The length arc produced by `radius` sweeping `angle`
    func arcLength(
    radius: Measurement,
    angle: Measurement
    ) -> Measurement {
    ...
    }

    View Slide

  21. Letʼs use a typealias for Length and Angle so that I donʼt have to use ugly indentation.
    I didnʼt say they were bad, just that they need to be used with care. I already have a
    well formed type, Iʼm just using a more convenient alias to refer to it.
    But look at the documentation - the summary is enough to tell you what the function
    does. The types provide information about what types come in and what types come
    out.
    SELF DOCUMENTING METHODS
    typealias Length = Measurement
    typealias Angle = Measurement
    /// The length arc produced by `radius` sweeping `angle`
    func arcLength(radius: Length, angle: Angle ) -> Length {
    ...
    }

    View Slide

  22. You do lose a bit of contextual information. You have to look
    up what Length and Angle actually are.

    View Slide

  23. You canʼt mix types. Even though the underlying type is a
    Double, you canʼt unintentionally operate on different types.
    SAFER INTERACTION BETWEEN VALUES
    angle1 = Measurement(value: .pi, unit: UnitAngle.radians)
    angle2 = Measurement(value: .pi / 2, unit: UnitAngle.radians
    angle3 = Measurement(value: 45, unit: UniteAngle.degrees)
    let angle = angle1 + angle2 // !
    let angle = angle1 + angle3 // "

    View Slide

  24. Letʼs look at the method again. You only need to create a length and an angle that
    produces a known length. You donʼt need to worry about the whether itʼs in degrees, or
    radians. Inches or centimetres, because they are just interchangeable types.
    TESTING IS EASIER
    typealias Length = Measurement
    typealias Angle = Measurement
    /// The length arc produced by `radius` sweeping `angle`
    func arcLength(radius: Length, angle: Angle ) -> Length {
    ...
    }

    View Slide

  25. The Measurements API is new, but you could have had some of the benefits of type
    safety with a type that wraps a simple Double into a type. This is one way to do it on early
    Ones.
    WRAPPER TYPES
    struct Radian {
    let value: Double
    }
    struct Degree {
    let value: Double
    }

    View Slide

  26. You can add generic constraints to existing types that do nothing.
    The advantage of this is that the base type is shared and you
    have the benefits of writing extensions that apply to all types of
    generic type
    PHANTOM TYPES
    struct Radian {}
    struct Degree {}
    struct Angle {
    let value: Double
    }
    let radian = Angle(value: .pi)

    View Slide

  27. SUMMARY

    View Slide

  28. e.g. Donʼt just deserialise your JSON and pass around an array of dictionaries. Map them
    to a proper Model object, and use that. I wonʼt make you look at my face again, but Iʼve
    actually seen this.
    USE PROPER TYPES.

    View Slide

  29. It doesnʼt make types; just gives you a chance to use better names. It can
    make code easier to read, but you lose some local context. Use with care.
    TYPEALIAS IS NOT A
    TYPE GENERATOR.

    View Slide

  30. Itʼs a simple technique where a fully formed custom type
    might not be necessary.
    USE WRAPPERS OR
    PHANTOM TYPES TO
    QUICKLY CREATE NEW
    TYPES.

    View Slide

  31. It's not something to fight against. It makes your programs easier to read, easier to
    debug and less prone to 3am errors.
    I wanted to remind you that the type system makes for safer programming. That's not
    all it does - It also lets you use different programming paradigms such as Protocol
    Oriented Programming, extensions, generics. Invite me back sometime and I'll talk to
    you about them.
    THE TYPE
    SYSTEM IS YOUR
    FRIEND.

    View Slide

  32. THANK YOU
    Abizer Nasir ❦ @abizern ❦ abizern.org

    View Slide