Something you can’t see about my dotSwift talk on OOP in FP in Swift is that to make the conference more interesting while the AV was set up for the next speaker, Daniel Steinberg invited me over to a side table for a question and answer session. He had some great questions and I had some adequate answers; that is now lost to time.
One thing he asked was about how I do things differently when I’m working in Objective-C and in Swift, and I mentioned that I tend not to use the type system in ObjC and just call all of my variables id
unless they are C types or the compiler asks otherwise. You can see an example of this in my UIKonf 1995 talk.
I argue (back in 2018, at dotSwift, in the bit that was videoed) that all objects have the same type. Just as I can define the “type” Set
through its function signature:
typealias Set<T> = (T) -> Bool
so I can define the “type” object through its function signature. An object – any object – is a function that responds to messages by turning selectors into methods:
typealias Object = (Selector) -> IMP
Now if all objects have the same type, why would I want to use different types for different objects?
Of course, there are reasons to want to refine the definition of an object from “any object” to “an object like this”, but these refinements are inaccessible using Objective-C’s type system (or Java’s, or Swift’s, or most other programming languages). Any object responds to any message, but for the most part they respond by doing whatever the default error-raising behaviour is which is not particularly interesting and doesn’t solve your customer’s problem. So what we want to be able to say is “this object is one that responds to a particular collection of messages in a way that is what I need here”.
We have two tools available to us, and neither gives us an answer to that question. The first is the protocol (or Java interface): we can say “this object is one that has implementations of methods for a particular collection of messages”. That’s not the same as the question we want to answer – it says nothing about whether the object responds in the ways we want. It’s also not generally the correct answer – an object that has the methods we want and behaves in the expected way but that didn’t have the protocol conformance recorded at compile time (even if it conformsToProtocol:
at run time) does not satisfy the compiler type check for protocol conformance.
Even less generally useful is using the class name as the type. Classes are ways to say “here is a collection of objects that all have common behaviour and data”; well for starters I don’t care about the data, but also just because those objects have the properties I want, doesn’t mean that others outside that place in the inheritance tree don’t.
I also can’t rely on subtypes behaving as I need, but the compiler type checker will pretend that if I asked for an instance of one class, and got an instance of a subclass, then I got the thing I wanted. Inheritance, in many languages including Objective-C, Java and similar, is a way to borrow behaviour from one class in another class. If we also want refinement from inheritance we have to add a bunch of rules that almost every programming language not named after the designer of the Garabit Viaduct does not support.
So there are three imprecise ways to ask for objects by behaviour in Objective-C, and I choose the one with the least typing. Even once you amortise the cost of this blog post.