Ulrik's Blog
@ulrikdamm

Custom Swift types for fun and safety

Let’s say you’re writing a weather app in Swift. You will need some functions for loading the temperature from the internet, storing it locally, displaying it and do some collocations on it. Let’s say it looks like this:

func loadTemperature() -> Promise<Float>

func saveTemperature(temperature : Float)

func loadTemperature() -> Float?

func calculateWindChill(temperature : Float, windSpeed : Float) -> Float

func displayTemperature(temperature : Float)

This might look fine, until you ask the question: is the temperature stored in celsius or fahrenheit? Temperature is not just a number. It’s a number with a type. This type can be celsius, fahrenheit, kelvin, etc. Without a type, it’s really just a number without any context.

One way to handle this, which is a common solution in languages with inadequate type systems, is to use comments:

// Result is in Fahrenheit

func loadTemperature() -> Promise<Float>


// Pass temperature as Celsius

func saveTemperature(temperature : Float)


// Returns as Celsius

func loadTemperature() -> Float?


// Takes and returns values in Fahrenheit

func calculateWindChill(temperature : Float, windSpeed : Float) -> Float


// Displays a Celcius temperature

func displayTemperature(temperature : Float)

This solution is not only annoying, it’s also fragile. People may not read the comments, and just assume that their personal favorite is universally used for everything. Also, if we switch weather provider, and suddenly the result switches from one type to another, we might not remember to update the comments. Lastly, this isn’t enforced by the compiler. It’s not enforced by anybody, except if you have a sharp code reviewer.

Luckily, there is a better way. Let’s use the type system to help us with our types (seems obvious, doesn’t it?)

One way to do this is to just use a typealias.

typealias Celsius = Float

typealias Fahrenheit = Float

This is better than using comments, but this is not very safe either, since it’s still actually the same type, we can just freely convert values without problem.

let temperature = Fahrenheit(24)

displayTemperature(temperature)

Whoops: displayTemperature takes a Celsius parameter. But the compiler doesn’t complain, since it’s all just floats.

Instead, let’s create these as real, unique types:

struct Celsius {

let value : Float

}


struct Fahrenheit {

let value : Float

}

You can now create Celsius and Fahrenheit values by supplying a float value to the initializer:

let tempC = Celsius(25)

let tempF = Fahrenheit(100)

We can create a new initializer on Float to be able to convert back to floats:

extension Float {

init(_ temperature : Celsius) {

self = temperature.value

}

init(_ temperature : Fahrenheit) {

self = temperature.value

}

}

Our functions now looks like this:

func loadTemperature() -> Promise<Fahrenheit>

func saveTemperature(temperature : Celsius)

func loadTemperature() -> Celsius?

func calculateWindChill(temperature : Fahrenheit, windSpeed : Float) -> Fahrenheit

func displayTemperature(temperature : Celsius)

Type checked and self-documenting code. Nice! If we try to supply the wrong type to a function, we will get a compilation error.

To convert between the two types, we can make a constructor for each:

extension Celsius {

init(_ value : Fahrenheit) {

self.value = (Float(value) - 32) * (5 / 9)

}

}


extension Fahrenheit {

init(_ value : Celsius) {

self.value = Float(value) * (9 / 5) + 32

}

}

Now we can explicitly convert between the two types, and they will always get correctly computed:

let tempF = Fahrenheit(tempC)

With these types, we can now safely use our functions without worrying about which type of temperature we’re using:

loadTemperature().getValue { temperature in

saveTemperature(Celsius(temperature))

let windChilled = calculateWindChill(temperature, windSpeed: windSpeed)

displayTemperature(Celsius(windChilled))

}

You can also implement some more protocols and functions for your custom types, to be a good Swift citizen, such as Comparable and Equatable. If you’re a bit more advanced, also look at things like IntegerType, FloatingPointType and IntegerArithmeticType, which can make your types feel very integrated. Remember that all the built-in Swift types are just structs which implements some protocols. An example is implementing CustomStringConvertible (Printable in Swift 1.2) to print the value the correct way:

extension Celsius : CustomStringConvertible {

var description : String {

return "\(value)˚C"

}

}


extension Fahrenheit : CustomStringConvertible {

var description : String {

return "\(value)˚F"

}

}

If you want to take this a step further, you can also implement your own types for things like distance, weight, volume, etc. You can also then start to think about, what do you get when you divide, say, distance and time? Dividing one meter with one second should give you one meter per second, which is a velocity-type. This can look something like this:

func /(lhs : Meter, rhs : NSTimeInterval) -> Velocity


let distance = Meter(1)

let time = NSTimeInterval(1)

let velocity = distance / time

Have fun, and stay (type-) safe!