Monday, December 21, 2015

Swift Proposal for Default Final

Curt Clifton (tweet, comments):

That is, the suggestion is that subclassing classes and overriding methods should be implicitly banned unless the framework author takes specific action to permit such indignities. The idea has evolved a bit over the last two+ weeks, to suggest that this implicit ban only apply across module boundaries.

I was shocked to find many members of the Swift team at Apple agreeing with this suggestion. When I’d mentioned the suggestion to co-workers and to friends in the app development community, to a person they said it was ridiculous.

Greg Titus:

I can’t prove any causation, but I would certainly argue that the dynamic nature and possible overridability of even things that Apple doesn’t specifically intend to allow overriding is one of the primary reasons why AppKit has survived for 20+ years and spawned arguably the most successful application framework in history in UIKit. On the other hand, efficiency and safety have rarely been major issues.

TLDR: I don’t think using the design trade-offs of Array (which is, after all, a value type and can’t be subclassed anyway) inside stdlib, can be very usefully broadened to apply to reference types in application frameworks.

Jordan Rose:

Supporting arbitrary code injection into someone else’s framework is a non-goal for Swift, perhaps even an anti-goal. […] If you replace a method on someone else’s class, you don’t actually know what semantics they’re relying on. Of course Apple code will have bugs in it. Trying to patch over these bugs in your own code is (1) obviously not an answer Apple would support, but also (2) fraught with peril, and (3) likely to break in the next OS release.

Curt Clifton:

Presumably a goal for Swift is that application developers will use it to build user-facing apps for Apple’s platforms. And presumably a goal for Apple is that developers help promote Apple’s platforms by shipping apps that take advantage of the new OS features when they ship. I fear that you and others dramatically underestimate the difficultly of doing that. I acknowledge your three points. But understand that we are professionals trying to serve our mutual customers. Temporary hacks in the service of shipping is the nature of the business.

I don’t know how to make the case more strongly than I already have. This thread makes me worry that the team does not understand what it’s like for third party developers trying to serve our mutual customers.

Michael Buckley:

But if you’re working on a consumer app, I do think that it’s logical vtable dispatch is what you want most of the time. So in my experience, functions need to be virtual more often than not, and the C++ code I’ve seen would be shorter if you had to explicitly mark methods as nonvirtual rather than virtual.

Curt Clifton:

swizzling and aggressive subclassing are last resorts, but they are necessary in the current ecosystem.

We regularly ship multiple major products on OS release day. We know this stuff.

I could not agree with Clifton more strongly. It seems like there is a culture clash between the compiler developers, who want to increase the potential for optimization, and the app developers, who like things to be more flexible because they are building on top of an imperfect platform. Since Swift’s introduction, I have been arguing that message passing (i.e. the dynamic keyword) should be the default, rather than vtable dispatch. The idea of locking things down further, with final as the default, is insane.

Making code that is not the bottleneck faster is a small win compared with an increase in bugs that directly affect customers. Rose and Joe Groff are right that there are problems with patching. But if it’s temporary and done responsibly, it’s a good pragmatic solution. Bugs are inevitable, and sometimes it is months or years before they are fixed, so Apple should not stand in the way of people trying to mitigate them.

Wil Shipley:

It is a disturbing thought, but it’ll be a long time before frameworks are written in Swift.

I like Swift. It’s exciting to think of future frameworks that take full advantage of it. It’s scary, and ironic, to think about how this modern, safe language may actually lead to more brittle software.

Update (2015-12-22): Brent Royal-Gordon:

This means doing some dangerous overriding, yes. But a UI that breaks after an iOS upgrade is not nearly as dangerous to my business as a three-month delay while I reimplement half of UIKit because someone in Cupertino thought they knew what I need better than I do and turned off—or even worse, left turned off without a single thought—subclassing of UIBarButtonItem.

The bottom line is this: Your users like Swift’s strictures when they’re helpful. This stricture is not helpful. Library users don’t accidentally subclass things, and with the override keyword in Swift, they don’t accidentally override them either. And where it truly is important, for safety or for speed, to prevent subclassing, we already have final. Making it the default is less safety than suffering.

Jesse Larson:

My concern when I saw Swift was that it was done by compiler guys who don’t ship or maintain apps.

Jared Sinclair:

Final by default requires a lvl of transparency and perfection that Apple framework engineers cannot realistically provide

Update (2015-12-23): Curt Clifton and Drew Crawford suggest middleware as a possible solution.

Update (2016-01-08): See also: Accidental Tech Podcast.

Update (2016-01-09): Friedrich Markgraf:

Apple making framework bugs unfixable was my number one concern when Swift was announced.

Marcel Weiher:

Subclassing a type not intended for it may be unfortunate, it may be necessary/desirable. Either way it is fixable.

Not being able to subclass a class is not.

And when the new release comes there is time for 3rd parties to fix. This is different from not being able to fix.

8 Comments RSS · Twitter

I feel like we're arguing in the dark when it comes to system libraries because there aren't any of them yet. It's so easy to fear the unknown.

The reality is there is this `final` keyword in the language. If Apple thinks it's better to make most things final in their own Swift APIs, they will, regardless of what the default is. Also, you don't really know what a pure Swift framework from Apple will look like. It could be all structs (tying your hands), or all protocols (which could make things more flexible in many aspects). Protocol-oriented seem to be were Swift is trying to take us, so I think it's fair to expect this is where the future Swift system frameworks will go too.

Most non-system frameworks are open source and you ship them with your app. It won't be a big problem if you need to make something non-final in one of them. Also, if you find a bug there, you're going to directly fix the library instead of overriding or swizzling. I think those libraries would benefit from having things `final` by default performance-wise, and I can't see many drawbacks honestly.

@Michel I think the default matters because there is cognitive and syntactic overhead to overriding it with a keyword. The same result is possible with both defaults, but in practice I don’t think we’d end up in the same place.

Did anyone from the compiler team actually suggested locking down UIKIt?
Would be even possible to do that, given that dynamic will always support objc_* methods?

I get the feeling that the possibility discussed is rather theoretical. It may apply to "new (ObjC-less) Foundation", but is it such a big deal if we were not able to sublcass "new and improved NSURL"?

@Michael I agree that defaults matter, but I don't think the current Objective-C Cocoa frameworks are a good way to judge of what should be done. Swift frameworks are going to contain more structs and less classes, and ideally rely more on protocols than their Objective-C siblings that rely heavily on subclassing. Also, we should keep in mind that Objective-C frameworks are not going to suddenly become `final` when imported in Swift just because that's the default in Swift.

I agree `final` by default isn't the best idea. What has been floated too on the mailing list is "sealed" by default, meaning "you can subclass and override but only within the same module" by default. That means you don't have to bother yourself about this issue until comes the time to make classes and methods public in a library. That'd be much better than `final` by default for the cognitive and syntactic overhead.

@ilya No. I think Clifton’s concern, which I share, is more about the general attitude of the compiler team during this discussion. It seemed like they don’t understand or don’t appreciate the concerns of app developers. And so this raised a red flag about decisions that will be made in the future, e.g. for non-Objective-C frameworks.

Secondly, because dynamic is not the default—even for Objective-C classes—there is already a danger for the current frameworks if there is Swift code beneath an Objective-C API. The compiler can decide to bypass the runtime, and that makes patching unreliable.

@Michel The frameworks are in a different module than application code, so from that perspective aren’t final and sealed equivalent?

@Michael Yes, they're equivalent if there's a module boundary. Sealed is a better default than final because you don't have to mark things as subclassable/overridable within your own code, and also with whole module optimization it has the same advantages as final by default. That's what I meant.

[…] finished listening to the latest episode of ATP where the hosts had an interesting discussion about the proposal to mark classes “final” by default in Swift. Let me first say that this whole […]

[…] it’s absolutely analogous. It’s become obvious that “unanticipated” dynamism is not something that Apple wants to support with Swift. […]

Leave a Comment