[swift-evolution] [Proposal] Explicit Synthetic Behaviour
Vladimir.S
svabox at gmail.com
Mon Sep 11 16:04:58 CDT 2017
On 11.09.2017 21:55, Thorsten Seitz via swift-evolution wrote:
> I think I do understand Haravikk's argument (actually it seems quite straightforward
> to me).
>
> An example should be:
>
> struct Foo : Equatable {
> var x: Int
> var cachedLabel: String? = nil
>
> init(x: Int) {
> self.x = x
> }
>
> mutating func label() {
> if let label = cachedLabel {
> return label
> }
> let label = calculateLabel()
> cachedLabel = label
> return cachedLabel
> }
> }
>
> var foo1 = Foo(x: 1)
> var foo2 = Foo(x: 1)
> foo1 == foo2 // true
> var label = foo1.label()
> foo1 == foo2 // now false, due to cachedString being falsely included in the comparison
>
> The problem is that the developer was not required to implement the protocol and so
> might forget it.
> The difference to other default implementations is that those use the protocol itself
> as building blocks and so are correct with regards to the protocol's semantics,
> whereas the synthesized equality reaches deeply into the private innards of a struct
> and therefore is much more likely to be wrong as in the example above.
>
> Why not just write
>
> *struct* Foo : *deriving* Equatable {...}
>
> to request the synthesized implementation?
FWIW, +100. The same should be required for Codable. I support the opinion that
'synthesized' methods differs from protocol-default-implementation in what 'kind' of
data they use: defined by protocol itself or internals of the conformed type. And
this can lead to more un-expected problems.
If protocol is able to synthesize its requirements, it should require a
'deriving'-like marker when type conforms to it to make it absolutely clear what
happens here. It would be not a confusion point, but clarify the intention to better
understand the code.
Thinking about *future* custom protocols that could implement requirements in default
implementation by using macros/reflection, for me it seems like such protocol should
*also* somehow explicitly state that some requirements are auto-synthesized, probably
by conforming(derive) to some compiler-magic protocol 'AutoSynthesize'.
(i.e. 'protocol MySynthesizeable: AutoSynthesize {...}')
So each built-in protocol like Equatable/Hashable/Codable will conform to it, and
also, each custom "auto-synthesizeable" protocol - also should explicitly conform to
AutoSynthesize. So, when type conforms to it - such type should use 'deriving'-like
marker if auto-generation of methods is expected.
I also have a question regarding future direction of 'exclusion' of fields from being
included into auto-generated implementation of Equatable/Hashable/Codable/other.
If we'll have this 'deriving'-like marker, it seems naturally if we mark some member
with some kind of '@noderiving' marker, like here:
struct Foo : deriving Equatable {
var x: Int
var y: Int
var z: Int
@noderiving var cachedLabel: String? = nil
}
this @noderiving directive will work for protocols based on AutoSynthesize magic
protocol. I.e., if you construct your own protocol with auto-synthesizeable methods,
to be able to *know* which members should be 'excluded' for your implementation, you
should base your protocol on AutoSynthesize protocol.
I hope this makes any sense :-)
Vladimir.
>
> -Thorsten
>
>
> Am 09.09.2017 um 19:42 schrieb Xiaodi Wu via swift-evolution
> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>:
>
>>
>> On Sat, Sep 9, 2017 at 06:41 Haravikk via swift-evolution
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>
>>> On 9 Sep 2017, at 09:33, Xiaodi Wu <xiaodi.wu at gmail.com
>>> <mailto:xiaodi.wu at gmail.com>> wrote:
>>>
>>>
>>> On Sat, Sep 9, 2017 at 02:47 Haravikk via swift-evolution
>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>
>>>
>>>> On 9 Sep 2017, at 02:02, Xiaodi Wu <xiaodi.wu at gmail.com
>>>> <mailto:xiaodi.wu at gmail.com>> wrote:
>>>>
>>>> On Fri, Sep 8, 2017 at 4:00 PM, Itai Ferber via
>>>> swift-evolution<swift-evolution at swift.org
>>>> <mailto:swift-evolution at swift.org>>wrote:
>>>>
>>>>
>>>>
>>>>> On Sep 8, 2017, at 12:46 AM, Haravikk via swift-evolution
>>>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>
>>>>>
>>>>>> On 7 Sep 2017, at 22:02, Itai Ferber <iferber at apple.com
>>>>>> <mailto:iferber at apple.com>> wrote:
>>>>>>
>>>>>> |protocol Fooable : Equatable { // Equatable is just a simple
>>>>>> example var myFoo: Int { get } } extension Fooable { static func
>>>>>> ==(_ lhs: Self, _ rhs: Self) -> Bool { return lhs.myFoo ==
>>>>>> rhs.myFoo } } struct X : Fooable { let myFoo: Int let myName:
>>>>>> String // Whoops, forgot to give an implementation of == }
>>>>>> print(X(myFoo: 42, myName: "Alice") == X(myFoo: 42, myName: "Bob"))
>>>>>> // true|
>>>>>> This property is/necessary/, but not/sufficient/to provide a
>>>>>> correct implementation. A default implementation might be able
>>>>>> to/assume/ something about the types that it defines, but it does
>>>>>> not necessarily know enough.
>>>>>
>>>>> Sorry but that's a bit of a contrived example; in this case the
>>>>> protocol should*not* implement the equality operator if more
>>>>> information may be required to define equality. It should only be
>>>>> implemented if the protocol is absolutely clear that .myFoo is the
>>>>> only part of a Fooable that can or should be compared as equatable,
>>>>> e.g- if a Fooable is a database record and .myFoo is a primary key,
>>>>> the data could differ but it would still be a reference to the same
>>>>> record.
>>>>>
>>>>> To be clear, I'm not arguing that someone can't create a regular
>>>>> default implementation that also makes flawed assumptions, but that
>>>>> synthesised/reflective implementations*by their very nature have
>>>>> to*, as they cannot under every circumstance guarantee correctness
>>>>> when using parts of a concrete type that they know nothing about.
>>>> You can’t argue this both ways:
>>>>
>>>> * If you’re arguing this on principle, that in order for
>>>> synthesized implementations to be correct, they/must/ be able to
>>>> —/under every circumstance/ — guarantee correctness, then you
>>>> have to apply the same reasoning to default protocol
>>>> implementations. Given a default protocol implementation, it is
>>>> possible to come up with a (no matter how contrived) case where
>>>> the default implementation is wrong. Since you’re arguing this/on
>>>> principle/, you cannot reject contrived examples.
>>>> * If you are arguing this/in practice/, then you’re going to have
>>>> to back up your argument with evidence that synthesized examples
>>>> are more often wrong than default implementations. You can’t
>>>> declare that synthesized implementations are/by nature/incorrect
>>>> but allow default implementations to slide because/in practice/,
>>>> many implementations are allowable. There’s a reason why
>>>> synthesis passed code review and was accepted: in the majority of
>>>> cases, synthesis was deemed to be beneficial, and would provide
>>>> correct behavior. If you are willing to say that yes, sometimes
>>>> default implementations are wrong but overall they’re correct,
>>>> you’re going to have to provide hard evidence to back up the
>>>> opposite case for synthesized implementations. You stated in a
>>>> previous email that "A synthesised/reflective implementation
>>>> however may return a result that is simply incorrect, because it
>>>> is based on assumptions made by the protocol developer, with no
>>>> input from the developer of the concrete type. In this case the
>>>> developer must override it in to provide *correct* behaviour." —
>>>> if you can back this up with evidence (say, taking a survey of a
>>>> large number of model types and see if in the majority of cases
>>>> synthesized implementation would be incorrect) to provide a
>>>> compelling argument, then this is something that we should in
>>>> that case reconsider.
>>>>
>>>>
>>>> Well put, and I agree with this position 100%. However, to play devil's
>>>> advocate here, let me summarize what I think Haravikk is saying:
>>>>
>>>> I think the "synthesized" part of this is a red herring, if I understand
>>>> Haravikk's argument correctly. Instead, it is this:
>>>>
>>>> (1) In principle, it is possible to have a default implementation for a
>>>> protocol requirement that produces the correct result--though not
>>>> necessarily in the most performant way--for all possible conforming
>>>> types, where by conforming we mean that the type respects both the
>>>> syntactic requirements (enforced by the compiler) and the semantic
>>>> requirements (which may not necessarily be enforceable by the compiler)
>>>> of the protocol in question.
>>>>
>>>> (2) However, there exist *some* requirements that, by their very nature,
>>>> cannot have default implementations which are guaranteed to produce the
>>>> correct result for all conforming types. In Haravikk's view, no default
>>>> implementations should be provided in these cases. (I don't necessarily
>>>> subscribe to this view in absolute terms, but for the sake of argument
>>>> let's grant this premise.)
>>>>
>>>> (3) Equatable, Hashable, and Codable requirements are, by their very
>>>> nature, such requirements that cannot have default implementations
>>>> guaranteed to be correct for all conforming types. Therefore, they should
>>>> not have a default implementation. It just so happens that a default
>>>> implementation cannot currently be written in Swift itself and must be
>>>> synthesized, but Haravikk's point is that even if they could be written
>>>> in native Swift through a hypothetical reflection facility, they should
>>>> not be, just as many other protocol requirements currently could have
>>>> default implementations written in Swift but should not have them because
>>>> they cannot be guaranteed to produce the correct result.
>>>>
>>>> My response to this line of argumentation is as follows:
>>>>
>>>> For any open protocol (i.e., a protocol for which the universe of
>>>> possible conforming types cannot be enumerated a priori by the protocol
>>>> designer) worthy of being a protocol by the Swift standard ("what useful
>>>> thing can you do with such a protocol that you could not without?"), any
>>>> sufficiently interesting requirement (i.e., one for which user ergonomics
>>>> would measurably benefit from a default implementation) either cannot
>>>> have a universally guaranteed correct implementation or has an
>>>> implementation which is also going to be the most performant one (which
>>>> can therefore be a non-overridable protocol extension method rather than
>>>> an overridable protocol requirement with a default implementation).
>>>
>>> You're close, but still missing key points:
>>>
>>> 1. I am not arguing that features like these should*not* be provided, but
>>> that they should*not* be provided implicitly, and that the developer
>>> should actually be allowed to request them. That is exactly what this
>>> proposal is about, yet no matter what I say everyone seems to be
>>> treating me like I'm against these features entirely; *I am not*.
>>>
>>>
>>> You are entirely against Equatable having a default implementation for ==.
>>> This is unequivocally stated. Others favor such a default implementation and
>>> feel that in the absence of a way to spell this in Swift itself, it should be
>>> magic for the time being. For the purposes of this argument it really is not
>>> pertinent that you are not also against something else; you're asking us to
>>> discuss why you are against a particular thing that others are for.
>>
>> FFS, how much clearer can I make this? *I AM NOT AGAINST THE FEATURE.*
>> *
>> *
>> What I am against is the way in which it is being provided implicitly rather
>> than explicitly, in particular as a retroactive change to existing protocols in
>> a way that introduces potential for bugs that are currently impossible, but
>> also in general.
>>
>>
>> You are against a default implementation for ==, i.e. an implementation that is
>> provided for you if you conform a type to the protocol and do nothing else
>> ("implicitly rather than explicitly"), and you are against the default
>> implementation being on the existing protocol Equatable ("retroactive change"). So,
>> to summarize, what you are against is precisely a default implementation for the ==
>> requirement on Equatable.
>>
>> This is the topic of discussion here; I am attempting to convince you that you
>> should be for rather than against these things.
>>
>>
>>> As repeatedly answered by others, nothing here is specific to synthesized
>>> default implementations, as more powerful reflection will gradually allow them
>>> to be non-synthesised.
>>
>> And as repeatedly stated by me; I am not treating synthesised vs. run-time
>> reflection any differently, I specifically included both in the original proposal.
>>
>>> As pointed out very cogently by Itai, you assert but offer no evidence, either
>>> in principle or empirically, that going too far by reflection is worse than
>>> going not far enough without reflection in terms of likelihood of a default
>>> implementation being inappropriate for conforming types.
>>
>> As I have also repeatedly pointed out it is not an issue of "not going far
>> enough" vs. "going too far"; if a default implementation lacks information then
>> it should not be provided, doing so regardless is a flaw in the protocol design
>> and not something that this proposal attempts to address (as such a thing is
>> likely impossible).
>>
>>
>> Right, one must consider the semantics of the specific protocol requirement and ask
>> whether a reasonable default can be provided for it.
>>
>> Reflective implementations *necessarily* go too far, because they literally
>> know *nothing* about the concrete type with any certainty, except for the
>> properties that are defined in the protocol (which do not require reflection or
>> synthesis in the first place).
>>
>>
>> I am confused why you are trying to argue in general terms about the universe of
>> all possible default implementations that use reflection. This is necessarily a
>> more difficult argument to make, and if it is to be convincing for all default
>> implementations it must also be convincing for the two specific protocol
>> requirements we are talking about here. Start small:
>>
>> We have agreed, as a community, that there is a reasonable default implementation
>> for Equatable.== when certain conditions are met (for value types only at the
>> moment, I believe). Namely, given two values of a type that has only Equatable
>> stored properties, those values are equal if their stored properties are all equal.
>> The author of a new value type who wishes to make her type Equatable but chooses
>> not to implement a custom == then benefits from this default when all stored
>> properties are Equatable.
>>
>> 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.
>>
>>> 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 ==?
>>
>>> And all of this continues to be a side-issue to the fact that in the
>>> specific case of Equatable/Hashable, which thus far has gone ignored, is
>>> that bolting this on retroactively to an existing protocol*hides bugs*.
>>> The issue of reflective default implementations is less of a concern on
>>> very clearly and well defined*new* protocols, though I still prefer more,
>>> rather than less, control, but in the specific case of*existing* protocols
>>> this fucking about with behaviours is reckless and foolish in the extreme,
>>> yet no-one on the core teams seems willing or able to justify it, which
>>> only opens much wider concerns (how am I to have any faith in Swift's
>>> development if the core team can't or won't justify the creation of new
>>> bugs?).
>>>
>>>
>>> This has emphatically not gone ignored, as I have myself responded to this
>>> point in an earlier thread in which you commented, as well as many others.
>>> Crucially, no existing conforming type changes its behavior, as they have all
>>> had to implement these requirements themselves. And as I said to you already,
>>> the addition of a synthesized default implementation no more "hides bugs"
>>> going forward than the addition of a non-synthesized default implementation to
>>> an existing protocol, and we do that with some frequency without even Swift
>>> Evolution review.
>>
>> Feel free to a supply a non-synthesised default implementation for Equatable
>> without the use of reflection. Go-on, I'll wait.
>> You insist on suggesting these are the same thing, yet if you can't provide one
>> then clearly they are not.
>>
>>
>> That is not the argument. The argument is that they are indistinguishable in the
>> sense that the author of a type who intends to supply a custom implementation but
>> neglects to do so will have a default implementation supplied for them. It is
>> plainly true that this is no more or less likely to happen simply because the
>> default implementation is synthesized.
>>
>>> 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.
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto: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
>
More information about the swift-evolution
mailing list