<div dir="ltr"><br><br><div class="gmail_quote"><div dir="ltr">On Tue, Sep 12, 2017 at 7:10 PM Xiaodi Wu <<a href="mailto:xiaodi.wu@gmail.com">xiaodi.wu@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Tue, Sep 12, 2017 at 9:58 AM, Thorsten Seitz via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="auto"><div></div><div>Good arguments, Tony, you have convinced me on all points. Transient is the way to go. Thank you for your patience!</div></div></blockquote><div><br></div></div></div></div><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div>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.</div></div></div></div></blockquote><div><br></div><div>Right—I hope it hasn't sounded like I'm conflating the two concepts completely. The reason I brought up "transient" is because nearly all of the "risky" examples being cited so far have been of the variety "I have a type where some properties happen to be Equatable but shouldn't be involved in equality", so my intention has been to show that if we have a better solution to that specific problem (which is, related to but not the same as the question at hand), then there aren't enough risky cases left to warrant adding this level of complexity to the protocol system.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div><br></div><div>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.)</div></div></div></div></blockquote><div><br></div><div>I totally agree with this. A design that would try to annotate "transient" with a protocol or list of protocols is missing the point of the semantics that "transient" is supposed to provide. It's not a series of switches to that can be flipped on and off for arbitrary protocols—it's a semantic tag that assigns additional meaning to properties and certain protocols (such as Equatable, Hashable, and Codable, but possibly others that haven't been designed yet) would have protocol-specific behavior for those properties.</div><div><br></div><div>To better explain what I've been poking at, I'm kind of extrapolating this out to a possible future where it may be possible to more generally (1) define custom @attributes in Swift, like Java annotations, and then (2) use some metaprogramming constructs to generate introspective default implementations for a protocol at compile-time just as the compiler does "magically" now, and the generator would be able to query attributes that are defined by the same library author as the protocol and handle them accordingly.</div><div><br></div><div>In a world where that's possible, I think it's less helpful to think in terms of "I need to distinguish between conforming to X and getting a synthesized implementation and conforming to X and avoiding the synthesized implementation because the default might be risky", but instead to think in terms of "How can I provide enough semantic information about my types to remove the risk?"</div><div><br></div><div>In other words, the switches we offer developers to flip shouldn't be about turning on/off entire features, but about giving the compiler enough information to make it smart enough that we never need to turn it off in the first place. As I alluded to before, if I have 10 properties in a type and only 1 of those needs to be ignored in ==/hashValue/whatever, writing "Equatable" instead of "derives Equatable" isn't all that helpful. Yes, it spits out an error message where there wouldn't have been one, but it doesn't reduce any of the burden of having to provide the appropriate manual implementation.</div><div><br></div><div>But all that stuff about custom attributes and metaprogramming introspection is a big topic of it's own that isn't going to be solved in Swift 5, so this is a bit of a digression. :)</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="auto"><div></div><div>-Thorsten</div><div><div class="m_8426129136578141142h5"><div><br>Am 12.09.2017 um 16:38 schrieb Tony Allevato via swift-evolution <<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>>:<br><br></div><blockquote type="cite"><div><div dir="ltr"><br><br><div class="gmail_quote"><div dir="ltr">On Mon, Sep 11, 2017 at 10:05 PM Gwendal Roué <<a href="mailto:gwendal.roue@gmail.com" target="_blank">gwendal.roue@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><blockquote type="cite"><div><div dir="ltr"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><br></div><div></div><blockquote type="cite"><div>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.</div></blockquote><div><br></div></div><div style="word-wrap:break-word"><div>And so everybody has to swallow implicit and non-avoidable code synthesis and shut up?</div></div></blockquote><div><br></div><div>That's not what I said. I simply pointed out one of the barriers to getting a new protocol added to the language.</div><div><br></div><div>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.</div></div></div></div></blockquote><div><br></div></div></div><div style="word-wrap:break-word"><div><div>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".</div><div><br></div><div>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.</div><div><br></div><div>I did use "non-avoidable" badly, you're right: one can avoid it, by providing its custom implementation.</div><div><br></div><div>So the code synthesis out of a mere protocol adoption *is* implicit.</div></div></div><div style="word-wrap:break-word"><div><div><br></div><blockquote type="cite"><div dir="ltr"><div class="gmail_quote"><div>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.</div></div></div></blockquote><div><br></div></div></div><div style="word-wrap:break-word"><div>Yes.</div><div></div></div><div style="word-wrap:break-word"><div><br><blockquote type="cite"><div dir="ltr"><div class="gmail_quote"><div>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?</div></div></div></blockquote><div><br></div></div></div><div style="word-wrap:break-word"><div><div>That's quite a difficult job: do you think you can evaluate this likelihood?</div><div><br></div><div>Explicit synthesis has big advantage: it avoids this question entirely.</div><div><br></div><div>Remember that the main problem with slide (2) is that developers can not *learn* to avoid it.</div><div><br></div><div>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:</div><div>- developer doesn't see the problem (a programmer mistake)</div><div>- the developper plans to add explicit conformance later and happens to forget (carelessness)</div><div>- a developper extends an existing type with a transient property, and doesn't add the explicit protocol conformance that has become required.</div><div><br></div><div>Case 2 and 3 bite even experienced developers. And they can't be improved by learning.</div><div><br></div><div>Looks like the problem is better defined as an ergonomics issue, now.</div></div></div><div style="word-wrap:break-word"><div><br><blockquote type="cite"><div dir="ltr"><div class="gmail_quote"><div>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.</div></div></div></blockquote><br></div></div><div style="word-wrap:break-word"><div></div><div>There is this sample code by Thorsten Seitz with a cached property which is quite simple and clear : <a href="https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170911/039684.html" target="_blank">https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170911/039684.html</a></div><div><br></div><div>This is the sample code that had me enter the "worried" camp.'</div></div></blockquote><div><br></div><div>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.)</div><div><br></div><div>Let's look at two scenarios:</div><div><br></div><div>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.</div><div><br></div><div>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.</div><div><br></div><div>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.</div><div><br></div><div>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 <b>"how do we give Swift the additional semantic information it needs to make the appropriate decision about what to synthesize?"</b></div><div><br></div><div>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.</div><div><br></div><div>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.</div><div><br></div><div>Here's a fun exercise—you can actually write something like "transient" without any additional language support today: <a href="https://gist.github.com/allevato/e1aab2b7b2ced72431c3cf4de71d306d" target="_blank">https://gist.github.com/allevato/e1aab2b7b2ced72431c3cf4de71d306d</a>. 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.</div><div><br></div><div>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.</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><div style="margin:0px;font-size:14px;line-height:normal;font-family:'Helvetica Neue';color:rgba(0,0,0,0.85098)"><br></div></div><div>Gwendal</div><div><br></div></div></blockquote></div></div>
</div></blockquote></div></div><span><blockquote type="cite"><div><span>_______________________________________________</span><br><span>swift-evolution mailing list</span><br><span><a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a></span><br><span><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a></span><br></div></blockquote></span></div><br>_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
<br></blockquote></div></div></div></blockquote></div></div>