Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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 }

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

All good. ✅

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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. !

Slide 11

Slide 11 text

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 }

Slide 12

Slide 12 text

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 }

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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 }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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 { ... }

Slide 21

Slide 21 text

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 { ... }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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 // "

Slide 24

Slide 24 text

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 { ... }

Slide 25

Slide 25 text

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 }

Slide 26

Slide 26 text

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)

Slide 27

Slide 27 text

SUMMARY

Slide 28

Slide 28 text

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.

Slide 29

Slide 29 text

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.

Slide 30

Slide 30 text

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.

Slide 31

Slide 31 text

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.

Slide 32

Slide 32 text

THANK YOU Abizer Nasir ❦ @abizern ❦ abizern.org