[swift-evolution] [Proposal] Explicit Synthetic Behaviour

Xiaodi Wu xiaodi.wu at gmail.com
Tue Sep 12 21:09:54 CDT 2017

On Tue, Sep 12, 2017 at 9:58 AM, Thorsten Seitz via swift-evolution <
swift-evolution at swift.org> wrote:

> Good arguments, Tony, you have convinced me on all points. Transient is
> the way to go. Thank you for your patience!

On many points, I agree with Tony, but I disagree that "transient"
addresses the issue at hand. The challenge being made is that, as Gwendal
puts it, it's _unwise_ to have a default implementation, because people
might forget that there is a default implementation. "Transient" only works
if you remember that there is a default implementation, and in that case,
we already have a clear syntax for overriding the default.

As others point out, there's a temptation here to write things like
"transient(Equatable)" so as to control the synthesis of implementations on
a per-protocol basis. By that point, you've invented a whole new syntax for
implementing protocol requirements. (Ah, you might say, but it's hard to
write a good hashValue implementation: sure, but that's adequately solved
by a library-supplied combineHashes() function.)

> -Thorsten
> Am 12.09.2017 um 16:38 schrieb Tony Allevato via swift-evolution <
> swift-evolution at swift.org>:
> On Mon, Sep 11, 2017 at 10:05 PM Gwendal Roué <gwendal.roue at gmail.com>
> wrote:
>>> This doesn't align with how Swift views the role of protocols, though.
>>> One of the criteria that the core team has said they look for in a protocol
>>> is "what generic algorithms would be written using this protocol?"
>>> AutoSynthesize doesn't satisfy that—there are no generic algorithms that
>>> you would write with AutoEquatable that differ from what you would write
>>> with Equatable.
>>> And so everybody has to swallow implicit and non-avoidable code
>>> synthesis and shut up?
>> That's not what I said. I simply pointed out one of the barriers to
>> getting a new protocol added to the language.
>> Code synthesis is explicitly opt-in and quite avoidable—you either don't
>> conform to the protocol, or you conform to the protocol and provide your
>> own implementation. What folks are differing on is whether there should
>> have to be *two* explicit switches that you flip instead of one.
>> No. One does not add a protocol conformance by whim. One adds a protocol
>> conformance by need. So the conformance to the protocol is a *given* in our
>> analysis of the consequence of code synthesis. You can not say "just don't
>> adopt it".
>> As soon as I type the protocol name, I get synthesis. That's the reason
>> why the synthesized code is implicit. The synthesis is explicitly written
>> in the protocol documentation, if you want. But not in the programmer's
>> code.
>> I did use "non-avoidable" badly, you're right: one can avoid it, by
>> providing its custom implementation.
>> So the code synthesis out of a mere protocol adoption *is* implicit.
>> Let's imagine a pie. The whole pie is the set of all Swift types. Some
>> slice of that pie is the subset of those types that satisfy the conditions
>> that allow one of our protocols to be synthesized. Now that slice of pie
>> can be sliced again, into the subset of types where (1) the synthesized
>> implementation is correct both in terms of strict value and of business
>> logic, and (2) the subset where it is correct in terms of strict value but
>> is not the right business logic because of something like transient data.
>> Yes.
>> What we have to consider is, how large is slice (2) relative to the whole
>> pie, *and* what is the likelihood that developers are going to mistakenly
>> conform to the protocol without providing their own implementation, *and*
>> is the added complexity worth protecting against this case?
>> That's quite a difficult job: do you think you can evaluate this
>> likelihood?
>> Explicit synthesis has big advantage: it avoids this question entirely.
>> Remember that the main problem with slide (2) is that developers can not
>> *learn* to avoid it.
>> For each type is slide (2) there is a probability that it comes into
>> existence with a forgotten explicit protocol adoption. And this probability
>> will not go down as people learn Swift and discover the existence of slide
>> (2). Why? because this probability is driven by unavoidable human behaviors:
>> - developer doesn't see the problem (a programmer mistake)
>> - the developper plans to add explicit conformance later and happens to
>> forget (carelessness)
>> - a developper extends an existing type with a transient property, and
>> doesn't add the explicit protocol conformance that has become required.
>> Case 2 and 3 bite even experienced developers. And they can't be improved
>> by learning.
>> Looks like the problem is better defined as an ergonomics issue, now.
>> If someone can show me something that points to accidental synthesized
>> implementations being a significant barrier to smooth development in Swift,
>> I'm more than happy to consider that evidence. But right now, this all
>> seems hypothetical ("I'm worried that...") and what's being proposed is
>> adding complexity to the language (an entirely new axis of protocol
>> conformance) that would (1) solve a problem that may not exist to any great
>> degree, and (2) does not address the fact that if that problem does indeed
>> exist, then the same problem just as likely exists with certain
>> non-synthesized default implementations.
>> There is this sample code by Thorsten Seitz with a cached property which
>> is quite simple and clear : https://lists.swift.org/
>> pipermail/swift-evolution/Week-of-Mon-20170911/039684.html
>> This is the sample code that had me enter the "worried" camp.'
> I really like Thorsten's example, because it actually proves that
> requiring explicit derivation is NOT the correct approach here. (Let's set
> aside the fact that Optionals prevent synthesis because we don't have
> conditional conformances yet, and assume that we've gotten that feature as
> well for the sake of argument.)
> Let's look at two scenarios:
> 1) Imagine I have a value type with a number of simple Equatable
> properties. In a world where synthesis is explicit, I tell that value type
> to "derive Equatable". Everything is fine. Later, I decide to add some
> cache property like in Thorsten's example, and that property just happens
> to also be Equatable. After doing so, the correct thing to do would be to
> remove the "derive" part and provide my custom implementation. But if I
> forget to do that, the synthesized operator still exists and applies to
> that type. If you're arguing that "derive Equatable" is better because its
> explicitness prevents errors, you must also accept that there are possibly
> just as many cases where that explicitness does *not* prevent errors.
> 2) Imagine I have a value type with 10 Equatable properties and one
> caching property that also happens to be Equatable. The solution being
> proposed here says that I'm better off with explicit synthesis because if I
> conform that type to Equatable without "derive", I get an error, and then I
> can provide my own custom implementation. But I have to provide that custom
> implementation *anyway* to ignore the caching property even if we don't
> make synthesis explicit. Making it explicit hasn't saved me any work—it's
> only given me a compiler error for a problem that I already knew I needed
> to resolve. If we tack on Hashable and Codable to that type, then I still
> have to write a significant amount of boilerplate for those custom
> operations. Furthermore, if synthesis is explicit, I have *more* work
> because I have to declare it explicitly even for types where the problem
> above does not occur.
> So, making derivation explicit is simply a non-useful dodge that doesn't
> solve the underlying problem, which is this: Swift's type system currently
> does not distinguish between Equatable properties that *do* contribute to
> the "value" of their containing instance vs. Equatable properties that *do
> not* contribute to the "value" of their containing instance. It's the
> difference between behavior based on a type and additional business logic
> implemented on top of those types.
> So, what I'm trying to encourage people to see is this: saying "there are
> some cases where synthesis is risky because it's incompatible with certain
> semantics, so let's make it explicit everywhere" is trying to fix the wrong
> problem. What we should be looking at is *"how do we give Swift the
> additional semantic information it needs to make the appropriate decision
> about what to synthesize?"*
> That's where concepts like "transient" come in. If I have an
> Equatable/Hashable/Codable type with 10 properties and one cache property,
> I *still* want the synthesis for those first 10 properties. I don't want
> the presence of *one* property to force me to write all of that boilerplate
> myself. I just want to tell the compiler which properties to ignore.
> Imagine you're a stranger reading the code to such a type for the first
> time. Which would be easier for you to quickly understand? The version with
> custom implementations of ==, hashValue, init(from:), and encode(to:) all
> covering 10 or more properties that you have to read through to figure out
> what's being ignored (and make sure that the author has done so correctly),
> or the version that conforms to those protocols, does not contain a custom
> implementation, and has each transient property clearly marked? The latter
> is more concise and "transient" carries semantic weight that gets buried in
> a handwritten implementation.
> Here's a fun exercise—you can actually write something like "transient"
> without any additional language support today: https://gist.github.com/
> allevato/e1aab2b7b2ced72431c3cf4de71d306d. A big drawback to this
> Transient type is that it's not as easy to use as an Optional because of
> the additional sugar that Swift provides for the latter, but one could
> expand it with some helper properties and methods to sugar it up the best
> that the language will allow today.
> I would wager that this concept, either as a wrapper type or as a built-in
> property attribute, would solve a significant majority of cases where
> synthesis is viewed to be "risky". If we accept that premise, then we can
> back to our slice of pie and all we're left with in terms of "risky" types
> are "types that contain properties that conform to a certain protocol but
> are not really transient but also shouldn't be included verbatim in
> synthesized operations". I'm struggling to imagine a type that fits that
> description, so if they do exist, it's doubtful that they're a common
> enough problem to warrant introducing more complexity into the protocol
> conformance system.
>> Gwendal
>> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170912/cafa4d08/attachment.html>

More information about the swift-evolution mailing list