[swift-evolution] [Proposal] Explicit Synthetic Behaviour
Vladimir.S
svabox at gmail.com
Tue Sep 12 05:32:13 CDT 2017
On 12.09.2017 0:35, Tony Allevato wrote:
>
>
> On Mon, Sep 11, 2017 at 2:05 PM Vladimir.S via swift-evolution
> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>
> 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.
>
>
> 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.
>
OK, got it, it was general thoughts, not exact proposal regarding the AutoSynthesize
protocol. Probably it should be @autosynthesize directive for protocol when you
define it or other 'marker', so when you conform to this protocol, you *can*
explicitely use 'derived'-like keyword to make requirements auto-synthesized,
otherwise you'll be asked by compiler for manual implementation.
> 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.
>
>
> This is something I mention in the original proposal, and I agree that it would be
> nice to have added later since there are clear known use cases where it's important.
>
> However, the feature shouldn't be tied *specifically* to derived implementations
> (meaning it shouldn't be named that way). What we're really talking about is
> "transient" data—data that exists for the purposes of caching/performance/etc. but
> which does not actually contribute to the thing's "value".
>
> The fact that transient data should not be ignored for equality, hashing, and
> serialization just happens to align with the protocols that we auto-synthesize so
> far, but it's not necessarily limited to those use cases. If an attribute is added
> for something like this, it should be *semantic* rather than speak to implementation
> details. In other words, it would be inappropriate to say "exclude this property from
> synthesized operations", but it would be fine to say "this property is transient
> data" and it just so happens that Equatable, Hashable, and Codable use that
> information to control what they synthesize.
>
> All this is a subtle, but important, distinction. One day, when Swift has the ability
> to introspect metadata about a type and its properties, someone may want to use a
> hypothetical "transient" attribute for something wholly unrelated to synthesis.
I see your points, but is it not possible that we want to exclude some property for
Equatable but keep it for Codable, or vise-versa, for example? So, actually, IMO we
need a way to exclude property from some specific protocol/protocols. Like
@transient var ...
@transient(for:Equatable) var ...
@transient(for:Codable) var ...
@transient(for:SomeOtherAuto) var ...
And again, I'm not proposing some concrete syntax/keyword, but think we'll need(in
future,yes, but IMO better to discuss now) a way to 'exclude' specific property from
specific auto-synthesizeable protocol.
Just my 2 cents why I believe we need an explicit 'derived'-like keyword when we want
auto-synthesized requirement for protocol:
* This will not change the logic/behavior of 'normal' conformance to
Equatable/Hashable, i.e. if you conform the type to them - you need to provide an
implementation OR (new text in warning message will be added) "use 'derived' keyword
to "auto-synthesize" these methods.
I.e. auto-synthesize will be something separate, additional, that you can use if you
want it, your clear choose.
It will not interfere with your 'usual' model of protocol conformance.
* You explicitly mark which protocol you want to be synthesized for you. For example,
if I see "struct S: derived Equatable, Codable" - I understand that Equatable will be
"generated" and Codable will be implemented manually in code.
* This makes code more clean, more understandable by reader, less error-prone, more
explicit on intention. The code *will* be better.
* Without explicit 'derived'-like keyword, the code will be worse : less explicit
about intention, more error-prone, hiding important details from reader, etc.
* And the 'derived'-like keyword is good price to have better code in this case.
Just my IMOs. Thank you for reading.
Vladimir.
>
>
> 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>
> <mailto: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>
> <mailto: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>
> >>> <mailto: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>
> <mailto: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>
> >>>> <mailto: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>
> >>>> <mailto: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>
> <mailto: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>
> >>>>>> <mailto: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>
> <mailto: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>
> <mailto: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 <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
More information about the swift-evolution
mailing list