[swift-evolution] Enums and Source Compatibility
Rex Fenley
rex at remind101.com
Wed Sep 20 17:51:45 CDT 2017
Hi Jordan,
I've been keeping up with most of the discussion since I last emailed about
my concern about making nonexhaustive the default mode for enums. So far I
am still strongly in the camp of exhaustive by default.
Most of my understanding comes from the perspective of source
compatibility; since I'm primarily an iOS app developer and contributor to
open source through CocoaPods, I don't have enough experience with binary
compatibility issues to understand the breadth of the effects of such a
change on binary compatibility - so I will argue from the perspective of
source compatibility. Additionally, all the following applies exclusively
to enums from Swift and not Obj-C/C. Largely my concerns stem from the "default
effect <https://en.m.wikipedia.org/wiki/Default_effect_(psychology)>"-
whatever default is chosen is going to unconsciously bias developers to use
that default.
To start, I don't follow how nonexhaustive by default leads to more secure
code or promises less. In a world where nonexhaustive is default,
hard-to-track bugs will be introduced frequently into many Swift
developer's code.
- Evolving contracts - The change you suggest leaves it up to the user
to remember to add new cases into their code if an enum ever does change in
a framework, and their code relies on exhaustive pattern matching.
- Unhandled case bugs - This will lead to inconsistencies and
hard-to-track bugs, since it would require the user to then track down an
unhandled case that they may not even be aware exists (forcing googling,
searching through documentation, etc.).
This is something that I certainly have experienced in the past working
across teams programming in Obj-C, and Swift has so far completely
eliminated this class of bugs! In a world where exhaustive remains default,
nothing changes, fewer bugs.
Next off is developer usability. As someone who has contributed to several
frameworks in Swift, one thing I know is that you can always count on
people complaining about the usability of their library - it's something
you learn to expect. If it turns out that people are very frustrated with
an enum constantly changing and breaking compatibility, they will voice
that concern with a thousand đź‘Ť on github, debate will happen, and the
appropriate course correction will be made. In this case, no real damage
done.
That said, if enums are nonexhaustive by default, frameworks will have more
nonexhaustive enums (as it becomes convention). The class of bugs
previously discussed will emerge, causing real damage to users and
developers. Complaints will arise, but with more hostility. In this case,
we end up back where we started, since framework developers will then mark
exhaustive for all their enums. The only difference is that it'll be
something that must be remembered to be done.
I can understand how someone developing an Apple framework may want
nonexhaustive by default, since enums from some Apple libraries seem to be
much larger and change relatively often. Yet, from my experience, this
doesn't represent what you find in open source libraries that seem to land
on something consistent and then stick with it. And that consistency pairs
very well with exhaustiveness.
Given this, it's clear that adding a case to an enum as a source breaking
change should be the expected behavior. It's safer for those using the
frameworks, and back propagation from users will correct it if it becomes
an annoyance. Nonexhaustive as a keyword is a nice additional tool to make
this correction simpler (as well as protect C enums by default), but should
not be the standard.
Best,
Rex
On Fri, Sep 15, 2017 at 5:06 PM, Jordan Rose <jordan_rose at apple.com> wrote:
> Hi, Rex. I definitely agree that 'exhaustive' is the right model for a
> multi-module app; indeed, there's no real reason for a single project to do
> anything else. However, it is not always the right behavior for libraries
> that actually get distributed, whether as source or as binary. In this case
> we want to minimize the error of omission: in the app case, forgetting
> "exhaustive" is an annoyance that you notice and fix once across your code
> base, but in the library case forgetting the "default case" means putting
> out a source-breaking release, and for libraries that have binary
> compatibility constraints there's no recourse at all.
>
> While most of the proposal deals with the experience we've had with the
> Apple SDKs (as written in Objective-C), we actually *have* run into this
> case in Swift already. The Swift Playgrounds app comes with a framework,
> PlaygroundSupport, that can be used from within a playground. It's
> important that when they upgrade the app, existing playgrounds don't break,
> since the end user may not have access to the entire code of the
> playground. (Remember that playgrounds are often authored by one developer
> or group, but then run and modified by someone else with a much lower skill
> level!) *That* means that PlaygroundSupport can't currently vend any
> enums that they expect playground authors to exhaustively switch over.
>
> (And to make it even more specific—and appealing—one of the enums they
> were considering would be a representation of the Swift AST. This can
> obviously change from release to release, but previous switch statements
> should stay valid.)
>
> Now, this is an example we know about, so we could certainly make it
> explicitly non-exhaustive. But in general we're in the same situation as
> 'open': if we want to be friendly to library authors, we need to make the
> default thing be the one that promises less, even if it means a bit of
> extra work in the "I-actually-own-everything" case.
>
> Best,
> Jordan
>
>
> On Sep 15, 2017, at 15:47, Rex Fenley <rex at remind101.com> wrote:
>
> Hey Jordan,
>
> Thank you for the time writing this up. I've been following along to the
> discussion somewhat closely and have kept silent because `exhaustive` was
> originally set to be the default for enums. However, that changed and so
> I'd like to voice my opinion, I frankly don't like this idea.
>
> At remind we use algebraic data types religiously for managing state and
> data and rely on exhaustive pattern matching to guarantee we're handling
> all states in our code. We're splitting out our code across modules and
> having this guarantee has been a joy to work with.
>
> The benefit of making nonexhaustive the default for Swift 5 across all
> multi-module code (besides C code) seems minimal to me. If a developer
> feels like they're unnecessarily managing enum cases, they can simply add a
> `default` case whenever they please. This is already the case and I'm
> curious if there's every been any complaints about this and what they would
> be. I'd prefer to be cautious and force exhaustive pattern matching in all
> possible cases and leave it up to the developer to choose not to.
>
> Ideally in my mind, these keywords won't be necessary. All Swift enums
> will remain as they are, exhaustively pattern matched by default. Enums
> from C code will be explicitly nonexhaustive in all cases.
>
> --
> Rex Fenley | IOS DEVELOPER
>
> Remind.com <https://www.remind.com/> | BLOG <http://blog.remind.com/> |
> FOLLOW US <https://twitter.com/remindhq> | LIKE US
> <https://www.facebook.com/remindhq>
>
>
>
--
Rex Fenley | IOS DEVELOPER
Remind.com <https://www.remind.com/> | BLOG <http://blog.remind.com/>
| FOLLOW
US <https://twitter.com/remindhq> | LIKE US
<https://www.facebook.com/remindhq>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170920/4b7de162/attachment.html>
More information about the swift-evolution
mailing list