Create a gist now

Instantly share code, notes, and snippets.

Phantom Types with Named Tuples

After implementing the typed notification observer pattern in my own project, I was pleasantly surprised to discover it was even more expressive than I initially thought. The original article presented the following example usage with an NSError type:

let globalPanicNotification: Notification<NSError> = Notification(name: "Global panic")
postNotification(globalPanicNotification, myError)

Because I'm a total OOP lackey, I refactored that into an instance method for my own use:

globalPanicNotification.post(myError)

But I kept the observer the same as the article, handling the notification through a block:

NotificationObserver(notification: globalPanicNotification) { err in
     println(err.localizedDescription)
}

With all the types checked at compile time, I thought this was pretty boss. Then, I had a use case where I wanted to send multiple objects via notification. I was going to do the standard, write-a-struct-wrapper solution I've done so many times before when I thought, can I do this through tuples?

let tupleNotification: Notification<(String, Double)> = Notification(name: "Tuple Notification")

Cool, it compiles. How about named parameters?

let tupleNotification: Notification<(description: String, value: Double)> = Notification(name: "Tuple Notification")

Nice, still compiles, so now I can just call the post method, and it will only compile if I provide the correct tuple WITH the argument labels:

tupleNotification.post((description: "Tuples rule", value: 5.0))

A little ugly...it won't accept it if I drop extraneous parenthesis, will it?

tupleNotification.post(description: "Tuples rule", value: 5.0)

Yes, it compiles! And how will the observer ingest this? Can I drop the parenthesis there too?

NotificationObserver(notification: tupleNotification) { description, value in
     println("description: \(description), value: \(value)")
}

Totally sweet, but what if I just want to send a notification with no data payload? Simple, just use Void, which is a type alias for an empty tuple.

let noDataNotification: Notification<Void> = Notification(name: "no data")

This will let you make a post call with empty input, and pass in a block into the observer with type () -> (), exactly what you want:

noDataNotification.post()

NotificationObserver(notification: noDataNotification) {
     println("No data")
}

So, to really hammer home how awesome this is: You can use this pattern with either labeled tuples or Void to define a completely arbitrary set of method input parameters per phantom type but still have all of your method invocations be completely validated at compile time. To me, this is why I put up with all of Swift's growing pains: You simply can't write as expressive and safe code with Objective-C.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment