[swift-evolution] [Proposal] Explicit Synthetic Behaviour

Xiaodi Wu xiaodi.wu at gmail.com
Tue Sep 12 21:26:43 CDT 2017

On Tue, Sep 12, 2017 at 11:43 AM, Haravikk via swift-evolution <
swift-evolution at swift.org> wrote:

> On 12 Sep 2017, at 12:08, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> On Mon, Sep 11, 2017 at 06:03 Haravikk via swift-evolution <
> swift-evolution at swift.org> wrote:
>> See, this is another flawed assumption; you are assuming that omitting a
>> custom implementation of == is always intentional rather than an oversight,
>> which is not guaranteed. This is one of my gripes with the retroactive
>> change to Equatable, as it is currently *impossible* to omit an
>> implementation.
> Again, this applies equally to the addition of _any_ default
> implementation. And again, such changes don’t even require Swift Evolution
> approval.
> So what? Because the Swift Evolution process is currently deficient we
> should just give up on discussing problems with features and the language
> altogether?

I don't claim that it's a deficiency; I claim it's reflective of Swift's
opinionated take on default implementations. Are you, after all, saying
that you have a problem with the addition of _any_ default implementation
to an existing protocol? If so, this conversation isn't about
synthesis/reflection at all.

And precisely what kind of "evidence" am I expected to give? This is a set
>> of features that *do not exist yet*, I am trying to argue in favour of
>> an explicit end-developer centric opt-in rather than an implicit protocol
>> designer centric one. Yet no-one seems interested in the merits of allowing
>> developers to choose what they want, rather than having implicit behaviours
>> appear potentially unexpectedly.
>> Both options were examined for Codable and for Equatable/Hashable. The
>> community and core team decided to prefer the current design. At this
>> point, new insights that arise which could not be anticipated at the time
>> of review could prompt revision. However, so far, you have presented
>> arguments already considered during review.
>> And so far all I have heard about this is how it was "decided"; no-one
>> seems interested in showing how any of these concerns were addressed (if at
>> all), so as far as I can tell they were not, or they were wilfully ignored.
> They were addressed by being considered.
> And yet no-one can apparently summarise what those "considerations" might
> be, suggesting that they were either *not* considered at all, or that the
> "consideration" was so weak that no-one is willing to step forward to
> defend it. Either way it is not sufficient by any reasonable measure.
> If I were to run over your foot in my car, would you be happy to accept
> that I "considered" it first?

How do you mean? People wrote in with their opinions. Then, taking into
account the community's response, the proposal was approved.

Therefore, your argument reduces to one about which default implementations
>>> generally ought or ought not to be provided--that is, that they ought to be
>>> provided only when their correctness can be guaranteed for all (rather than
>>> almost all) possible conforming types. To which point I sketched a rebuttal
>>> above.
>>> If a protocol defines something, and creates a default implementation
>>> based only upon those definitions then it must by its very nature be
>>> correct. A concrete type may later decided to go further, but that is a
>>> feature of the concrete type, not a failure of the protocol itself which
>>> can function correctly within the context it created. You want to talk
>>> evidence, yet there has been no example given that proves otherwise; thus
>>> far only Itai has attempted to do so, but I have already pointed out the
>>> flaws with that example.
>>> The simple fact is that a default implementation may either be flawed or
>>> not within the context of the protocol itself; but a reflective or
>>> synthetic implementation by its very nature goes beyond what the protocol
>>> defines and so is automatically flawed because as it does not rely on the
>>> end-developer to confirm correctness, not when provided implicitly at least.
>> Again, if it applies generally, it must apply specifically. What is
>> "automatically flawed" about the very reasonable synthesized default
>> implementation of ==?
>> It makes the assumption that every equatable property of a type is
>> necessarily relevant to its equality.
> No necessarily, only provisionally and rebuttably. If it’s not the case,
> override the default.
> So… entirely unlike standard default implementations which *cannot* "provisionally"
> assume something is relevant at all,

Why not?

> thereby making them entirely different from synthesised/reflective
> implementations!
> I'm sorry, but you keep trying to argue that they're the same, but then
> admitting that they're not. You can't have it both ways.

Well, certainly, synthesized default implementations differ from
non-synthesized ones in key respects. However, they do not differ in terms
of the user experience of conforming to the protocol and having to override
the default.

Consider for example if a type stores a collection index for performance
>> reasons; this isn't an intrinsic part of the type, nor relevant to testing
>> equality, yet this default implementation will treat it as such because it
>>  *knows nothing about the concrete type's properties*. If a protocol
>> does not define a property then any action taken upon such a property is
>> necessarily based upon an assumption; just because it might be fine some of
>> the time, does not make it any less flawed.
>> The big difference here between explicit and implicit synthetic
>> implementations is where this assumption originates; if a method is
>> synthesised implicitly then the assumption is made by the protocol designer
>> alone, with no real involvement by the end developer. If I explicitly
>> opt-in to that default however I am signalling to the protocol that it is
>> okay to proceed. In the former case the assumption is unreasonable, in the
>> latter it is explicitly authorised. It is a difference between "I want to
>> make the decision on what's correct" and "I am happy for you (the protocol
>> designer) to decide".
>> Right now, when I conform to Equatable, it is a declaration of "I will
>> implement this", but with this retroactive implicit change it is now a
>> declaration of "implement this for me", these are two entirely different
>> things. Consider; what if I'm working on a piece of code that requires
>> types to be Equatable, but one of the types I'm using currently isn't, so I
>> quickly throw Equatable conformance onto it and go back to what I was
>> doing, with the intention of completing conformance later. With this change
>> that type may now receive a default implementation that is wrong, and I've
>> lost the safety net that currently exists.
> Right now, it still wouldn’t compile, so I don’t see why you would do
> that. In the future, if you want to make it not compile, there is nothing
> stopping you from conforming to a non-existent “NotYetEquatable”. This was
> something that you asked about earlier and it was answered.
> So your solution is to intentionally write invalid code to work around the
> fact that a feature is being implemented badly?

You stated a use case where you *want* the compiler to stop your code from
compiling by stating a conformance to Equatable without implementing its
requirements. You then stated that the major problem you have with
synthesized `==` is that the compiler will now use a default implementation
that you might forget about instead of stopping compilation. Therefore, I
demonstrated how you could continue to have the compiler stop your code
from compiling. It's not my solution that is intentionally writing invalid
code; your stated aim was to be able to do so.

> FFS, if that is considered a "solution" then my faith in the direction of
> Swift is plummeting rapidly. Why even have rules? Let's just fuck about
> with everything and let the developers pick up the mess!
> A non-synthesised/reflective implementation cannot strictly be incorrect,
>> because as long as it is implemented properly it will always be correct
>> within the context of the protocol itself. It may not go quite as far as an
>> end developer might want, but that is because they want to add something
>> onto the protocol, not because the protocol is wrong.
>> A synthesised/reflective implementation differs because if it goes too
>> far it is wrong not only within the context of the concrete type, but also
>> the protocol itself, it is simply incorrect.
> Again, this is an assertion that misses the mark. If the default
> implementation is unsuitable for a type, it’s unsuitable whether it
> “doesn’t go quite as far” or “goes too far.”
> Because not going quite far enough is not a failure of the protocol, as
> protocols by their very nature can only go as far as what they define. If a
> protocol Foo defines two properties, a method which uses those two
> properties correctly, then the method is correct. A developer of a concrete
> type might want to add more information or tailor the behaviour, but that
> doesn't make the default implementation incorrect, it's just considering
> the type only within the context of being an instance of Foo.
> Going too far is the opposite; it's the protocol designer messing around
> with stuff they do not define at all. It's only ever right by chance, as
> it's operating within the context of the concrete type, about which the
> protocol does not know anything with certainty.

Yes, you have defined "not going far enough" and "going too far" based on
whether an implementation uses only protocol requirements or not. However,
you haven't at all demonstrated why this distinction is at all meaningful
in terms of the issue you describe with a user conforming to a protocol. If
there is a default implementation, either it returns the expected result
for the conforming type or it does not--those are the only two choices. Are
you arguing that, empirically, the default implementation for Equatable
will more often be unsuitable for conforming types? If so, what's your

You state but do not give any rationale for the claim that the former is
> not wrong in some context while the latter is always wrong.
> By this line of argumentation, you’d be perfectly content if instead we
> simply had the default implementation of == as “return true” because it
> would be somehow not wrong.
> Only if return true were a reasonable default to give in the context of
> the protocol, which it clearly is not, as it's not performing any kind of
> comparison of equality.

Sure it is; `return true` satisfies all the semantic requirements for
equality: reflexivity, symmetry, transitivity; and, in the context of the
protocol which only provides for this one facility (determination of
equality or inequality), any two instances that compare equal _are_
completely interchangeable "within the context of the protocol itself," as
you would say.

Put another way, what the proposal about synthesizing implementations for
>>> Equatable and Hashable was about can be thought of in two parts: (a) should
>>> there be default implementations; and (b) given that it is impossible to
>>> write these in Swift, should we use magic? Now, as I said above, adding
>>> default implementations isn't (afaik) even considered an API change that
>>> requires review on this list. Really, what people were debating was (b),
>>> whether it is worth it to implement compiler-supported magic to make these
>>> possible. Your disagreement has to do with (a) and not (b).
>>> Wrong. The use of magic in this case produces something else entirely;
>>> that's the whole point. It is *not the same*, otherwise it wouldn't be
>>> needed at all. It doesn't matter if it's compiler magic, some external
>>> script or a native macro, ultimately they are all doing something with a
>>> concrete type that is currently not possible.
>>> And once again; *I am not arguing against a default implementation that
>>> cuts boilerplate*, I am arguing against it being implicit. What I want
>>> is to be the one asking for it, because it is not reasonable to assume that
>>> just throwing it in there is always going to be fine, because it quite
>>> simply is not.
>> If you have to ask for it, then it's not a default. You *are* against a
>> default implementation.
>> A default implementation is an implementation that I, as the concrete
>> type developer, do not have to provide myself. If you want default to mean
>> only "automatic" then your attempt to pigeon-hole what I am arguing is
>> incorrect, because what I am arguing is then neither about default
>> implementations nor the means of actually implementing it, but something
>> else entirely.
>> But as far as I'm concerned it still absolutely still a default
>> implementation whether it is requested or not; the difference is I, as the
>> end developer, am able to refine what type of defaults that I want.
> The word “default” indicates something that arises in the absence of a
> user indication otherwise.
> Then this proposal is just for a different mechanism for "indicating
> otherwise".
> You keep trying to argue that a synthesised/reflective default
> implementation is the same as a normal default implementation, yet you seem
> to be consistently forgetting that even if that is true without this
> proposal, that the very proposal itself is to change that, effectively
> causing a category of default implementation to become explicitly
> opted-into, rather than implicitly. They're still implementations that will
> be provided automatically, just only when they are permitted to do-so.

So to be clear, you are *against* them being the *default*: you wish them
to be the *otherwise*.

> On 9 Sep 2017, at 23:17, Gwendal Roué <gwendal.roue at gmail.com> wrote:
>> All right, I'll be more positive: our science, IT, is a *constructive*
>> science, by *essence*. If there is a problem, there must be a way to show
>> it.
>> It you can't, then there is no problem.
>> You mean just as I have asked for examples that prove
>> non-synthetic/reflective default implementations are as dangerous as
>> synthetic/reflective ones? Plenty have suggested this is the case yet no
>> reasonable examples of that have been given either.
>> However, examples highlighting problems with the synthesised behaviour
>> are simple:
>> struct Foo : Equatable { var data:String } // Currently an error, won't
>> be in future
>> Or something a bit more substantial:
>> struct KeyPair : Equatable {
>> static var count:Int = 0
>> var count:Int
>> let key:String // This is the only property that should be equatable
>> var value:String
>> init(key:String, value:String) {
>> let count = KeyPair.count &+ 1
>> KeyPair.count = count; self.count = count
>> self.key = key; self.value = value
>> }
>> }
>> Here the only important property in the key pair is the key, the value
>> isn't important (only the keys are to be considered unique) and the count
>> is just a throwaway value. The synthesised default implementation for this
>> concrete type will therefore be completely wrong, likewise for Hashable,
>> which will likely produce radically different results for instances that
>> should be the same.
> I notice that despite asking endlessly for examples, the ones I've given
> are being ignored. In future I shall remind people asking for examples
> where they can shove them.
> _______________________________________________
> 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/5b6ef40b/attachment.html>

More information about the swift-evolution mailing list