[swift-evolution] [Proposal] Explicit Synthetic Behaviour

Vladimir.S svabox at gmail.com
Wed Sep 13 12:19:28 CDT 2017


On 13.09.2017 19:08, Ondrej Barina via swift-evolution wrote:
> Maybe something like this as middle ground.
> 
> protocol Equatable {
>      @syntetic static func ==(_ lhs: Self, _ rhs: Self) -> Bool
> }
> 
> protocol itself contains default implementation, but without real body. Instead the 
> function is marked that the real body is generated by compiler.
> There is explicit mentions of default impl (by compiler magic), but it does not 
> affects users as they would still use protocol in normal way:
> 
> struct Foo: Equatable { .... }

Yes, I also thought about this. And personally for me it is also good solution, while 
`struct S: Equatable {/*nothing*/}` will *still* lead to compiler's error or at least 
warning about not implemented requirements.
So, I'll be explicit regarding my intention: do I want requirements to be 
auto-generated or I want to do this manually.

But still. If you see

struct S: Equatable, Codable {
   // a lot of lines
}

you can't say right now if requirements for Equatable and/or Codable was implemented 
manually or will be auto-generated without checking all the code of a type. This 
knowledge can help to faster solve issues related to comparison/archiving.
So for me the best solution is still 'deriving'-like keyword, which adds clarity and 
show intention without any boilerplate code:

struct S: Equatable, deriving Codable {
   // all clear:
   // manually implemented Equatable
   // auto-generated Codable

   // a lot of lines
}

Vladimir.

> 
> Ondrej B.
> 
> On Wed, Sep 13, 2017 at 4:14 PM, Haravikk via swift-evolution 
> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
> 
>>     On 13 Sep 2017, at 03:26, Xiaodi Wu <xiaodi.wu at gmail.com
>>     <mailto:xiaodi.wu at gmail.com>> wrote:
>>
>>     On Tue, Sep 12, 2017 at 11:43 AM, Haravikk via
>>     swift-evolution<swift-evolution at swift.org <mailto:swift-evolution at swift.org>>wrote:
>>
>>
>>>         On 12 Sep 2017, at 12:08, Xiaodi Wu <xiaodi.wu at gmail.com
>>>         <mailto:xiaodi.wu at gmail.com>> wrote:
>>>
>>>>         On Mon, Sep 11, 2017 at 06:03 Haravikk via swift-evolution
>>>>         <swift-evolution at swift.org <mailto: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.
> 
>     No, and you should know that by now. I suggest actually reading some of what I
>     have written as I am sick of repeating myself.
> 
>>>>>>             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.
> 
>     I mean because not once have you summarised what these alleged "considerations"
>     were; if they exist then you should be able do so, yet all I am hearing is "it
>     was considered", which frankly is not an argument at all as it is entirely
>     without substance.
> 
>     If it was genuinely considered then someone should be able to say what points
>     were considered and what conclusions were reached and why. And even if there
>     *was* an earlier decision, that doesn't necessarily make it right. We are
>     discussing it now, and it is clear that any decision that has been made has been
>     made poorly at best.
> 
>     And if you're talking about the discussion on Equatable/Hashable specifically,
>     I'm afraid your memory of the "considerations" is radically different to mine; as
>     the concerns I raised were essentially ignored, as not a single person gave a
>     justification more substantial than "but, but Codable!" which frankly isn't a
>     justification at all.
> 
>>>>>>                 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?
> 
>     Because they can only act upon properties/methods that they themselves (or a
>     parent protocol) define. FFS, what is so unclear about that? Or are you arguing
>     on this subject without every having actually used a protocol before?
> 
>>         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.
> 
>     Except that that's not true at all, is it?
> 
>     Synthesised default implementations go much further in how they attempt (and
>     potentially fail) to implement those defaults, and in the specific case of
>     Equatable/Hashable they are fully implementing a protocol without a single
>     property of method being raised as a requirement; they are utterly different at a
>     fundamental level, no amount of mental contortion changes that fact.
> 
>>>>             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.
> 
>     My stated aim was nothing of the sort.
> 
>     I was pointing out that right now conforming to Equatable means something
>     entirely different from what it will mean in future if this idiotic change makes
>     it into release. Please actually read what I write before deciding for yourself
>     what my 'stated aim' is.
> 
>     I am *not* asking for workarounds to circumvent a ridiculously flawed change to
>     the language, I am arguing why it is flawed and must be changed. If I wanted a
>     workaround I'd do what I'm now seriously considering, which is ditching Swift
>     completely, as I will not use a language if I can no longer trust the team
>     developing it or the decisions that they make.
> 
>>>>             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 evidence?
> 
>     What's yours? If this issue was as "considered" as you constantly claim then
>     where is the evidence that there is no meaningful distinction? Surely such
>     evidence exists, or else the issue hasn't been considered at all, has it?
> 
>     Frankly I am sick of being asked to provide evidence when you are seemingly
>     unwilling to do anything in return, especially when you have conveniently ignored
>     every single example that I have already given.
> 
>     It cuts both ways; you claim that "going too far" and "not going far enough" are
>     the same thing? Well prove it.
> 
>>>         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.
> 
>     The purpose of Equatable is to identify types that can be compared for equality;
>     returning true does not satisfy that aim because no such comparison is occurring,
>     so your example is intentionally ridiculous. Even a less contrived example such
>     as comparing memory addresses doesn't fulfil the purpose of Equatable, which is
>     all about comparing equality of different instances that might still be the same.
> 
>>>>>                 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*.
> 
>     You seem to be insisting upon a narrow definition of default; what I want is
>     control over which types of default implementations are provided. Just because
>     they must be opted-into explicitly does not stop them being "default", as they
>     are still implementations that I myself do not need to implement. The difference
>     is that I want to actually *want* them rather than have provided through
>     potentially flimsy assumptions made by a protocol designer. Just because there's
>     an extra step doesn't make them any less automatic, otherwise having to conform
>     to a protocol in the first place would also prevent them from being defaults.
> 
>     Asking *for* something is more like a middle-ground between the two; the
>     synthetic implementations are still possible defaults, they just aren't provided
>     unless you allow them, while omitting the necessary keyword/attribute prevents
>     them being used.
> 
>>>>             On 9 Sep 2017, at 23:17, Gwendal Roué <gwendal.roue at gmail.com
>>>>             <mailto: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:
>>>
>>>                 structFoo :Equatable{vardata:String}// Currently an error, won't
>>>                 be in future
>>>
>>>
>>>             Or something a bit more substantial:
>>>
>>>                 structKeyPair :Equatable{
>>>                 staticvarcount:Int=0
>>>
>>>                 varcount:Int
>>>                 letkey:String// This is the only property that should be equatable
>>>                 varvalue:String
>>>
>>>                 init(key:String, value:String) {
>>>                 letcount =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.
> 
>     And once again, totally ignored. You seem to love asking for "evidence" but why
>     exactly should I bother giving anything if you ignore it when I try to?
> 
>     _______________________________________________
>     swift-evolution mailing list
>     swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>     https://lists.swift.org/mailman/listinfo/swift-evolution
>     <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
> 


More information about the swift-evolution mailing list