The Type System is Your Friend

Swift is strongly typed, but how do we use this to our advantage? In this talk, Johannes Weiß shows us how we can leverage the type system, to help you rapidly prototype the main functions of your app. We learn about the various uses of undefined, a handy placeholder for filling value and function holes in a compiler-checkable way, and we end with a brief demonstration of phantom types. Whatever your preconceptions, one thing is clear: the type system is most definitely your friend.

You can see Johannes’ “nano” framework for undefined() on GitHub.


Making the Illegal Impossible (0:00)

My talk was originally about Making the Illegal Impossible, but I had a slight problem of overlap with the brilliant talk by Brian Gesiak. So, I boiled down my old talk to this one slide. When you create a new type in your program, you should ask the question, “Does every value that can be created for the type make sense?”. As an example, returning an optional value and an optional NSError doesn’t really make sense. There are so many values that make absolutely no sense in your program. You have to check at run time.

Further Leveraging the Type System (1:09)

Now, I would like to go into my new talk, Further Leveraging the Type System. I will present tools that you can use straightaway if you like, they are not very complicated. The only things that these tools will have in common are the device base and type system. They would not have worked in Objective-C, but the new features in the Swift type system will allow you to use them.

Fill Holes With undefined (1:38)

The first thing I want to present is filling holes. This might seem quite strange, but is actually very useful. In your strictly typed program, you might have an expression that you know the type of, but you don’t know the implementation nor the value. This was a problem we saw in Chris Eidhof’s talk, where he had a function that needed to return a non-existent UIViewController. In his case, he was quite lucky because UIViewController has an empty initializer, so he could just return a UIViewController.

But that is not always the case, sometimes it may be quite complicated to create functions, and you will not know what to do. What I propose as a temporary fix is this weird function, undefined. This function can fill holes, and should only be used while writing your program.

undefined is quite an interesting function: it pretends to be able to return values of any type T. In truth, this is obviously not possible because there can’t be a new function that is able to return any type T without getting enough information to create any type T. Therefore, the implementation of undefined is basically just fatal error, and should crash any program.

func undefined<T>(_ message:String="") -> T {
    fatalError("undefined \(message)")
}

Now, you might ask yourself, “Why would I write a program that deliberate crashes every time I run it?”. That doesn’t make sense. The reason for this, again, is that it is meant to be used temporarily while you are developing your program. Quite often, you will run into situations where you get compiler errors, because you have to return a value from a function but you have yet to create a value for that type. That is when you can use undefined.

For example, say you have string a or integer b, but you don’t know the value of either. Use undefined. Or, suppose you want to create an NSTimer because you need one to use, but you have no clue what to put for its parameters. For now, you can simply use undefined as a placeholder. However, you will have to replace all of these instances of undefined with actual values in order to finish your program.

let a : String = undefined("special string")
let b : Int = undefined("some int")
let t = NSTimer(timeInterval: undefined(), target: undefined(),
                selector: undefined(), userInfo: undefined(),
                repeats: undefined())

Function Holes (3:51)

The interesting thing about using undefined in Swift is that it can do more than just return, or pretend to return, values. undefined can also act as a function. “Any type T” includes functions. For example, my function parseIntFromString might return an integer if it can parse the given string as a number. Now that I have this function, I want to use it elsewhere in the system, but I have yet to write the implementation. Instead of implementing it right now, I can tell the Swift compiler that undefined is now a function from String to Int. It will parse the input string and pretend to return an integer. This code will type check, the Swift compiler will be happy, and you can move on to more interesting parts of your application.

func parseIntFromString(input : String) -> Int? {
    let parsedInt = (undefined() as String -> Int?)(input)
    return parsedInt
}

In another example, we have a function that takes a list of integers and maps them through the mayFail function. mayFail might or might not return an integer with the type Result. The interesting thing here is that we perform complicated operations using map and reduce, and then we want a “fancy +” to add up successful values. For now, it doesn’t matter if we don’t have that, and so we use undefined. For extra convenience, I have given undefined a string parameter to annotate what the function will do once it has been implemented. When the program is executed, it will crash and give you this message: “fatal error: undefined fancy +: file main.swift, line 214”. What really matters is that you know the rest of your program makes sense, because the compiler could verify that all the types fit together. When everything compiles, then you’ve basically won because you know that, from a type perspective, your program is sound. You can then just replace all the undefineds with real code or values.

func mayFail(x : Int) -> Result<Int>
func addUpSuccesses(xs : [Int]) -> Int {
    let combine : (Int, Result<Int>) -> Int = undefined("fancy +")
    return map(xs, mayFail).reduce(0, combine:combine)
}

-> fatal error: undefined fancy +: file main.swift, line 214

I think using undefined is also a lot better than using, say, zero, nil, or the empty string. There are many values that you could use as placeholders. The problem with using them is finding them again in your code, which can be quite difficult. If you have a function that returns zero, that function might be correctly implemented, or it might not. When you use undefined, you can search for those later on and gradually replace them as you need.

Handling the Impossible (6:35)

The last use case for undefined that I want to present today is handling the impossible. Here, I have an example function called lexicographicallyLowestString. In one possible implementation of this, I take these three strings, pack them into an array, start the function sorted on the array, and then take the first string. This should be a correct implementation, but in Swift, the first property is an optional because without a first value, you might have an empty array.

However, in my case, I do know that there will be a first value. Here you use undefined to say that this is not possible, but you are not able to convince the compiler and prove that the list will not actually be empty. undefined is used to say that the impossible happened.

func lexicographicallyLowestString(x:String, y:String, z:String) -> String {
    let xyz = [x, y, z]
    return xyz.sorted({ $0 < $1 }).first ??
        undefined("the impossible happened!")
}

Phantoms (7:37)

The second item I want to talk about is phantoms, or rather, phantom types. Phantom types can be used to mark values at compiler time. One example of that is a type Money. Here, a Money is defined as a struct that holds an NSDecimalNumber. Now we know that money has some form of currency; $3 and £3 are not the same. They are not interchangeable. But still, they can be represented just the same by an NSDecimalNumber. So, in this case, I made a phantom type.

A phantom type is a parameterized type whose type parameters do not all appear in its definition. I have what you could call a useless type parameter C. This is the Currency, and it is not even used in the actual properties of the struct. To fill Money, I created an empty protocol, Currency, so that I may mark any type as a currency. The three enums GBP, EUR, and USD are my three currencies.

protocol Currency {}

enum GBP : Currency {}
enum EUR : Currency {}
enum USD : Currency {}

struct Money<C : Currency> {
    let amount : NSDecimalNumber
}

One interesting fact about all the three values is that you cannot create a value of the type GBP, EUR, or USD type because it is an enumeration with zero possible values. The phantom type is mostly for use at compile time, not at run time. There is no need to create a value. As an example of how this could be used, I might write the function convertGBPtoEUR that has a certain foreign exchange rate to convert from GBP to EUR. This rate will only work for those two currencies, and not for example, from USD to GBP. I then use this phantom type annotation of the currency in the Money type in this function.

func convertGBPtoEUR(gbp:Money<GBP>) -> Money<EUR> {
    let forex : NSDecimalNumber = NSDecimalNumber(mantissa: 133263,
                                                  exponent: -5,
                                                  isNegative: false)
    return Money(amount:gbp.amount.decimalNumberByMultiplyingBy(forex))
}

This function basically states that I can only convert GBP to EUR. In Xcode, I can define a variable fivePounds to have the amount 5 and the currency GBP. Likewise, the variable threeEuros has amount 3 and currency EUR. The third variable fivePoundsInEuro invokes convertGBPtoEUR on fivePounds. This will convert the type of fivePounds to EUR. However, if I try to invoke my convert function on threeEuros, the compiler will complain that this is probably not what you wanted.

Q&A (11:08)

Q: I noticed that you used the coalescing operator ?? in the example for handling the impossible. Why wouldn’t you use the bang operator ! instead?
Johannes: In a real implementation you would probably use the bang !. You would get a better error message, which is a valid point. But to be fair, this was just to make an example.


You can see Johannes’ “nano” framework for undefined() on GitHub.



Johannes Weiß

Johannes Weiß