Swift Protocols
WWDC 2015 Session 408 (video, PDF):
This is a static type safety hole. Why did it happen? Well, it’s because classes don’t let us express this crucial type relationship between the type of self and the type of other. In fact, you can use this as a “code smell.” So, any time you see a forced down-cast in your code, it’s a good sign that some important type relationship has been lost, and often that’s due to using classes for abstraction.
So when you see Self in a protocol, it’s a placeholder for the type that’s going to conform to that protocol, the model type.
[…]
Now, you might think that forcing the array to be homogeneous is too restrictive or, like, a loss of functionality or flexibility or something. But if you think about it, the original signature was really a lie.
[…]
So, once you add a Self-requirement to a protocol, it moves the protocol into a very different world, where the capabilities have a lot less overlap with classes. It stops being usable as a type.
[…]
We find that, the more we decouple things with protocols, the more testable everything gets. This kind of testing is really similar to what you get with mocks, but it’s so much better.
[…]
You might ask, ‘what does it means to have a requirement that’s also fulfilled immediately in an extension?’ Good question. The answer is that a protocol requirement creates a customization point.
[…]
Swift 1 had lots of generic free functions like this. In Swift 2, we used protocol extensions to make them into methods like this, which is awesome, right? […] No more angle bracket blindness.
[…]
So, building bridges between the static and dynamic worlds is a fascinating design space, and I encourage you to look into more.
Marcel Weiher (channeling Crusty):
You see, I presented ADT (Abstract Data Type) programming to him and called it OOP. It’s a little ruse I use from time to time, and decades of Java, C++ and C# have gone a long way to making it an easy one.
[…]
So not only isn’t inheritance not the defining feature of OOP as I let on, it actually wasn’t even in the original conception of the thing that was first called “object-oriented programming”.
[…]
Because the idea was really to first get him all excited about not needing OOP, and then turn around and show him that all the things I had just shown him in fact were OOP. And still are, as a matter of fact.
[…]
My personal take is that our biggest challenges are in “the big”, meaning programming in the large. How to connect components together in a meaningful, tractable and understandable fashion. Programming the components is, by and large, a solved problem, making it a tiny bit better may make us feel better, but it won’t move the needle on productivity.
Ok, so the heart, as I understood the talk, is about thinking of your types as in the form of protocols instead of base classes. The fundamental idea is to get rid of one of the really nasty problems of OOP - implicit data sharing. That’s great because that problem sucks.
It turns out, we can do this today in ObjC with one caveat - default protocol implementations. This is a feature that is new with Swift 2.0 and apparently wasn’t worth bringing back to ObjC.
[…]
I think the important takeaway from the Swift talk is not really about a “new paradigm” of programming, but rather showing a better way to compose software using techniques that we already use day-to-day. It makes it easier to do the better thing (get rid of accidental data sharing between types) and reducing the boiler-plate code required to do it.
There is one thing I’m worried about though: the class extension seems to be creating a very similar problem in that we are getting rid of unintentional data sharing between type hierarchies and replacing it with potentially unintentional functional changes in our programs.
I haven’t had a chance to play with it too much, but watching the Protocol-Oriented Programming in Swift session, a particular construct struck me as the most likely source of arcane, incomprehensible bugs in the future. I expect it to be the novice’s crucible, similar to the way deallocated delegates would lead to crashes in the days before the
weak
attribute was introduced. I’m not yet sure what the searches will look like, but the fundamental question will be a variation of:“Why does the method that I wrote overriding protocol extension X never get called?”
[…]
By always calling the value in the type’s implementation, then, we forever hide the default implementation of the extension, even in cases where it would be expected. The solution Swift 2 adopted is to call the default implementation when the protocol is explicitly specified.
[…]
The rules for dispatch for protocol extensions, then, are:
- IF the inferred type of a variable is the protocol:
- AND the method is defined in the original protocol
- THEN the runtime type’s implementation is called, irrespective of whether there is a default implementation in the extension.
- AND the method is not defined in the original protocol,
- THEN the default implementation is called.
- ELSE IF the inferred type of the variable is the type
- THEN the type’s implementation is called.
If you are like me and you prefer reading decisions from a flow chart, the above reasoning can summarised in the following flow chart[…]
This behavior is unsettling to me. For one, it makes some sense that you cannot change the functionality of another module with extensions to types belonging to that module. On the other hand, if I provide an extension for
Sheep
in my module, I’ll be able to use the new functionality just fine there, but anytime the type gets used in another module, the functionality will fall-back to the original behavior.This just sounds like a scary source of bugs waiting to happen. I think the solution might be to simply disallow extensions to protocols that are not defined within the same module. I rather lose out on potential functionality to maintain certain guarantees in my program.
With Swift 2, however, while we could continue to use decoration to wrap our data with new functionality, we’ve been given a new power with protocol extensions. Protocol extensions let us add concrete methods to a protocol that are dependent on the abstract methods in that protocol. It’s a form of the template method pattern, but one that doesn’t rely on inheritance.
[…]
Fortunately, we’re saved by protocol extensions here. We can give a default implementation for these properties in an extension, and we can leave it out of the individual request structs.
To put it another way, if your protocol really only has meaning within your type hierarchy, ask yourself if it really makes sense to make it a protocol. I don’t think an answer of, “well, I want my type to be a struct so I need to use a protocol here instead” is a good reason. Decompose it and make it more applicable if that’s really the case.
[…]
Hopefully this is a just a point-in-time problem, but as soon as you make your protocols generic, you lose the ability have typed collection of hetergenous instances of protocols. I consider this a serious design limitation. For instance, all of the non-
Self
constrained functionality of a protocol should be safely callable from any place that protocol wants to be used.This also applies to having your protocol adhere to generic protocols, such as
Equatable
. Generics are infectious.
Working with plain C structures is frequently a massive pain, so people often write “object oriented” wrappers around them to make them easier to work with. But then you run into leaky abstractions or impedance mismatch. In Swift, you can make the original raw C structs as easy to use as native Swift classes, and get the best of both worlds. Protocol conformance is a part of that.
So, yes, you can work with old-school BSD sockets code (this is a hypothetical example, don’t bug me about its utility) and throw sockaddrs into arrays, let Swift worry about managing their memory, diff those arrays to see what’s changed, and so on, and it all Just Works™.
I see this as one cornerstone of Swift’s “Grand Unified Theory” approach, where (however excited people might get by certain presentations) instead of saying Here Is The One New Way To Do Things, Swift instead allows you to mix and match whatever is appropriate to the task, whether that’s getting all functional, deeply dynamic, protocol-oriented, and more.
[…]
What you can’t do — and this is mostly where people are getting into trouble — is work with collections of arbitrary heterogenous generic protocols.
[…]
when you have an object of protocol type, that is actually a special object instance of about 40 bytes in size.
This is in turn due to that useful fact that anything, from an Int to a C struct to an Obj-C class can confirm to a protocol. You can’t simply cast an array of all those different types, to an array of the protocol, because the underlying data is completely different.
[…]
So, that’s why it stores these special Protocol instances instead, they’re adapters for the underlying types. And that’s why you can’t just cast arbitrary arrays to them. And why you can’t simply dispatch everything dynamically: some of those things just aren’t dynamic-able, because they might be C structs, Ints, native IEEE floating point values, or whatever, not Objective-C objects.
Something like this ought to come naturally and easily to a language, or else that language is not helping me write apps.
This isn’t some weird, made-up situation. It’s super-common. Look at Mail’s sidebar, for instance — there are a bunch of different things. (Or look at Xcode’s sidebar.)
Yes. There are ways to deal with this in Swift, including using @objc protocols and collections. Or proxy objects or base classes (ugh) or whatever.
Being a C# developer by day and a Swift developer by night has me constantly thinking about the similarities and differences between these two languages. I genuinely enjoy programming with each, and I love it when I can take a strategy that works well in one language, and see where that might cross over to the other. One of the areas I’ve been pondering as of late is the idea of how Swift and C# compare in terms of protocol extensions and abstract classes.
Previously: Swift Protocols, Arrays, and Casting, Swift Protocols Question, Swift Protocols and Generics.
Update (2015-09-04): Matthew Palmer:
This weekend, I rewrote most of Locksmith, my library for using the iOS keychain, to be protocol-oriented. This was a fascinating process, and one that isn’t yet finished.
[…]
Protocol oriented programming is the best approach I can think of to deal with this complexity and configurability.
Protocols in Swift can be generic via abstract type members rather than parameterisation. Consequently, the protocol itself can no longer be used as a type but only as a generic constraint.
[…]
If you’re interested in the tradeoffs between type parameterisation and abstract type members, check out the discussion on the dev forums and the article on how the issue affects Scala.
Update (2016-06-06): Ole Begemann:
I recently came across this post by Kevin Ballard on Swift Evolution that includes the best explanation I have seen for why method dispatch in protocols works the way it does […] So essentially, while protocols have a virtual function table, protocol extensions do not, and cannot easily have one because a type adopting the protocol won’t necessarily know about all extensions at compile time and therefore cannot add the extension methods to its own vtable.
Update (2017-03-15): Jonah Williams:
We’ve seen that subclasses cannot reliably override protocol methods they inherit from their parent class when those methods have default implementations in a protocol extension. This causes confusing behavior where a subclass can implement protocol methods only to discover that they are never called from behavior inherited from a superclass. This can be a source of confusing bugs and identifying the root cause requires inspecting the behavior of all our parent classes. Something that can be especially difficult if we were to subclass a framework provided class.
Update (2019-03-06): objc.io:
Swift Quiz
[…]
What’s the output?