<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On May 24, 2016, at 09:07, Tony Allevato &lt;<a href="mailto:allevato@google.com" class="">allevato@google.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="gmail_quote"><div dir="ltr" class="">On Mon, May 23, 2016 at 9:58 PM Jordan Rose &lt;<a href="mailto:jordan_rose@apple.com" class="">jordan_rose@apple.com</a>&gt; wrote:<br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex;"><div style="word-wrap: break-word;" class=""><div class="">[Proposal:<span class="Apple-converted-space">&nbsp;</span><a href="https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md" target="_blank" class="">https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md</a>]&nbsp;</div><div class=""><br class=""></div><div class="">Hi, Tony. Thanks for working on this. I have to say I’m incredibly concerned with this direction, for two main reasons. I’ll try to break them down here. (Sorry for squeaking in at the end of the review period!)</div></div></blockquote><div class=""><br class=""></div><div class="">No worries, thanks for the detailed feedback! I've tried to address your concerns inline.</div><div class=""><br class=""></div><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex;"><div style="word-wrap: break-word;" class=""><div class=""><b style="line-height: 1.5;" class="">Overrides</b><br class=""></div><div class=""><b class=""><br class=""></b></div><div class="">People up until now have been fairly unhappy with how operators are statically dispatched and can’t be overridden. We went all the way towards providing == that automatically calls isEqual(_:) on NSObject, but then you provide == for your subclass and it never gets called…no, wait, it<span class="Apple-converted-space">&nbsp;</span><i class="">does</i>&nbsp;get called when you do a simple test, but not from the actual code that has NSObject as a static type.</div><div class=""><br class=""></div><div class="">This proposal stays in that space: the proposed “trampoline” operator will dispatch based on the<span class="Apple-converted-space">&nbsp;</span><i class="">static</i>&nbsp;type of the objects, not the dynamic type. Why? Consider using == on an NSURL and an NSString, both statically typed as NSObject. Given the definition of the trampoline from the proposal</div><div class=""><br class=""></div><blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;" class=""></blockquote></div><div style="word-wrap: break-word;" class=""><blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;" class=""><div class="">func&nbsp;==&nbsp;&lt;T:&nbsp;Equatable&gt;(lhs: T, rhs: T)&nbsp;-&gt;&nbsp;Bool {</div></blockquote></div><div style="word-wrap: break-word;" class=""><blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;" class=""><div class="">&nbsp;<span class="Apple-converted-space">&nbsp;</span>return&nbsp;T.==(lhs, rhs)</div><div class=""><div class="">}</div></div></blockquote><div class=""><br class="">T can’t possibly be anything but NSObject. (Neither NSURL nor NSString matches the types of both ‘lhs’ and ‘rhs’.) This isn’t a regression from the current model, as you say, but it does make the current model<span class="Apple-converted-space">&nbsp;</span><i class="">even more surprising,</i>&nbsp;since normally you’d expect methods to be dynamically dispatched.</div></div></blockquote><div class=""><br class=""></div><div class="">Setting Objective-C aside for a moment, is this example consistent with Swift's design philosophies about type safety though? Swift doesn't even let you compare types that seem reasonably compatible, like Float and Double. Should we expect users to be able to compare completely distinct types like NSURL and NSString without a little extra work?</div><div class=""><br class=""></div><div class="">If having `==` map to `NSObject.isEqual` is important for Objective-C compatibility, then there's no reason the runtime can't provide the following operator:</div><div class=""><br class=""></div><div class="">func ==(lhs: NSObject, rhs: NSObject) -&gt; Bool {</div><div class="">&nbsp; return lhs.isEqual(rhs)</div><div class="">}</div><div class=""><br class=""></div><div class="">which should be more specific than the generic one and always get chosen in the context you desire.</div><div class=""><br class=""></div><div class="">The situation is a little better when one of the types being compared has the other type as a base, because then implementing the operators as class methods instead of static methods does the right thing (with the only caveat being you have to cast the arguments down to the correct type).</div></div></div></div></blockquote><div><blockquote type="cite" class=""><br class=""></blockquote></div><div><blockquote type="cite" class=""><div dir="ltr" class=""><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex;"><div class="" style="word-wrap: break-word;"><div class="" style="word-wrap: break-word;"><div class=""><br class=""></div><div class="">Here’s an alternate formation of the trampoline that’s a little better about this…</div><div class=""><b class=""><br class=""></b></div><div class=""><blockquote class="" style="margin: 0px 0px 0px 40px; border: none; padding: 0px;"></blockquote></div></div><div class="" style="word-wrap: break-word;"><div class=""><blockquote class="" style="margin: 0px 0px 0px 40px; border: none; padding: 0px;"><div class="">func&nbsp;==&nbsp;&lt;T:&nbsp;Equatable&gt;(lhs: T, rhs: T)&nbsp;-&gt;&nbsp;Bool {</div></blockquote></div></div><div class="" style="word-wrap: break-word;"><div class=""><blockquote class="" style="margin: 0px 0px 0px 40px; border: none; padding: 0px;"><div class="">&nbsp;&nbsp;return lhs.dynamicType.==(lhs, rhs)</div><div class="">}</div></blockquote></div><div class=""><br class=""></div><div class="">…but I’m still not convinced. (People are especially likely to get this wrong without the trampolines being synthesized.)</div><div class=""><br class=""></div></div></div></blockquote></div></div></blockquote><br class=""></div><div>Yeah, I don’t think arbitrary symmetric operators <i class="">do</i>&nbsp;work generally across object types. Equality is special since there’s a reasonable default, but that’s probably not the common case. That does make this a little better—if you never have to override an operator function, the static dispatch doesn’t matter.</div><div><br class=""></div><div>I’m still concerned about <i class="">asymmetric</i>&nbsp;operators, however. That includes assignment operators, but also things like operators for chaining tasks, DSLs for constraint systems, etc. It’s not too hard to implement these to forward to something dynamically dispatched (as we’ve both shown), but the default behavior, which currently doesn’t do what you want, will continue not doing what you want.</div><div><br class=""></div><blockquote type="cite" class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="gmail_quote"><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex;"><div style="word-wrap: break-word;" class=""><div class=""><b class="">Assignment Operators</b></div><div class=""><b class=""><br class=""></b></div><div class="">A mutating requirement and a static method with an inout parameter mean different things for a conforming class: the former can only access the class’s properties, while the latter can replace the caller’s<span class="Apple-converted-space">&nbsp;</span><i class="">reference</i>&nbsp;as well.</div><div class=""><br class=""></div><blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;" class=""><div class="">class Player { … }</div><div class=""><br class=""></div><div class="">extension Player {</div><div class="">&nbsp;<span class="Apple-converted-space">&nbsp;</span>static func roulette(_ player: inout Player) {</div><div class="">&nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>if randomValue() &gt; 0.1 {</div><div class="">&nbsp; &nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>player.roundsSurvived += 1</div><div class="">&nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>} else {</div><div class="">&nbsp; &nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>// Replace this player…but not any other references to them!</div><div class="">&nbsp; &nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>player = Player()</div><div class="">&nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>}</div><div class="">&nbsp;<span class="Apple-converted-space">&nbsp;</span>}</div><div class=""><br class=""></div><div class="">&nbsp;<span class="Apple-converted-space">&nbsp;</span>/*mutating*/ func solitaire() {</div><div class="">&nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>self.roundsSurvived += 1</div><div class="">&nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>// Cannot replace ‘self’</div><div class="">&nbsp; &nbsp;<span class="Apple-converted-space">&nbsp;</span>//self = Player()</div><div class="">&nbsp;<span class="Apple-converted-space">&nbsp;</span>}</div><div class="">}</div></blockquote><div class=""><br class=""></div><div class="">I’m not sure if one of these is obviously better than the other (more capable ↔︎ more footgun). I agree with Nicola's point about mutating methods<span class="Apple-converted-space">&nbsp;</span><i class="">looking</i><span class="Apple-converted-space">&nbsp;</span>better than static methods taking an inout parameter, but that probably shouldn’t be the ultimate deciding factor.</div></div></blockquote><div class=""><br class=""></div><div class="">You make a good point here.&nbsp;<span style="line-height: 1.5;" class="">This is hitting on one of the fundamental problems I've had with mutating requirements in protocols in some of my own projects, which is that even when a class type conforms to such a protocol (or worse, even if the protocol itself is restricted to `class` types only), if I want to use it in a generic context with that protocol as a constraint, I still have to pass it unnecessarily as an inout argument.</span></div><div class=""><br class=""></div><div class="">On the one hand, fixing that underlying problem might help us here as well. An obvious alternative would be to disallow `inout` on class conformances to assignment operators, but that introduces inconsistency that I'm trying to avoid. I'm not sure if it's any better, either.</div></div></div></blockquote><div><br class=""></div><div>Yeah, I’d definitely like to change the behavior we have now. We discussed it previously and decided this was the least bad thing to do, but I think that decision was wrong. That deserves a whole proposal of its own, though.</div><div><br class=""></div><br class=""><blockquote type="cite" class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class=""><br class=""></div><blockquote class="gmail_quote" style="margin: 0px 0px 0px 0.8ex; border-left-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: solid; padding-left: 1ex;"><div style="word-wrap: break-word;" class=""><div class=""><br class=""></div><div class="">I know we want to improve type-checker performance, and reducing the number of overloads<span class="Apple-converted-space">&nbsp;</span><i class="">seems</i>&nbsp;like a way to do that, but I’m not convinced it actually will in a significant way (e.g. “you can now use ten operators in a chain instead of seven” is not enough of a win). It still seems like there ought to be a lot of low-hanging fruit in that area that we could easily clear away, like “an overload containing a struct type will never match any input but that struct type”.</div><div class=""><br class=""></div><div class="">I personally really want to move operators into types, but I want to do it by doing member lookup on the type, and fall back to global operators only if something can’t be found there. That approach</div><div class=""><br class=""></div><div class="">- also has potential to improve type-checker performance</div><div class="">- also removes operators from the global namespace</div><div class="">- also removes the need for “override points” (implementation methods like NSObject.isEqual(_:) and FloatingPoint.isLessThan(_:))</div><div class=""><br class=""></div><div class="">It does privilege the left-hand side of a binary operator, but I think that’s acceptable for the operators we’ve seen in practice. (Of course we would need real data to back that up.)</div></div></blockquote><div class=""><br class=""></div><div class="">As Brent pointed out in his reply, without multiple dispatch, you don't really benefit from privileging the lhs argument, and in fact you can end up in situations where the behavior is surprising if you don't implement both orders. For example, in your (NSString, NSURL) example, each class would have to be extended to explicitly support comparison with the other in order to support commutativity if equality was an instance method. I'm not sure that's any better for those particular types than just having the operators global in the first place.</div></div></div></blockquote><div><br class=""></div><div>I assume you’d still have to implement it with NSObject as the “other” type, like you do in Objective-C. You’d just return false in that case. But you’d get the correct answer for comparing two NSURLs that are statically typed as NSObject, which you wouldn’t using the vanilla trampoline.</div><div><br class=""></div><br class=""><blockquote type="cite" class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="gmail_quote"><div class=""><br class=""></div><div class="">My argument would be that there are still significant enough benefits to move forward:</div><div class=""><br class=""></div><div class="">- for value types and for binary operators that have the same typed arguments, static methods provide a clear benefit w.r.t. consistency (the declaration of the global operator and the static operator look the same) and obvious semantics</div></div></div></blockquote><div><br class=""></div><div>Agreed…for operators that go with protocols. Is that all of them? Most of them? I’m not sure.</div><br class=""><blockquote type="cite" class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="gmail_quote"><div class="">- for binary operators with differently typed arguments, static operators support commutativity much better than instance methods because both implementations live in the most natural type for that particular case (for example, String&nbsp;+ Character and Character&nbsp;+ String)</div></div></div></blockquote><div><br class=""></div><div>Agreed, but I consider this only a mild plus because extensions are pretty easy in Swift. We put initializers and helper methods on related types all the time.</div><br class=""><blockquote type="cite" class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="gmail_quote"><div class="">- if later on we can auto-generate trampolines, the choice of instance vs. static methods for value types becomes a wash because the compiler is effectively doing the same dispatch under the hood anyway</div></div></div></blockquote><div><br class=""></div><div>That’s true for value types whether or not we autogenerate, isn’t it?</div><div><br class=""></div><div>(Also, for autogeneration, I like the idea of it being opt-out the same way default initializers are for a struct: if you provide a global operator implementation in the same module with the correct signature, the compiler doesn’t need to generate one.)</div><br class=""><blockquote type="cite" class=""><div dir="ltr" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><div class="gmail_quote"><div class="">- for class types, regardless of whether one is a base of the other or both share a common third base type, neither static nor instance methods completely solve the problem and won't until/unless Swift supports multiple dispatch, and the proposed behavior is not a regression in those cases</div></div></div></blockquote><br class=""></div><div>I guess I’m not convinced of the chain of reasoning here. “Multi-method dispatch is the most correct way to solve the problem” is fine; “therefore, anything short of that isn’t worth doing” is where I get stuck. Instance methods <i class="">partially</i>&nbsp;solve the problem, and it’s possible (again, no data on hand) that they solve the problem in the majority of cases.</div><div><br class=""></div><div>(It’s also possible that the prevalence of OO has made people prefer operators that can be dispatched based on the left-hand side, so I guess I’d want to go look at, e.g. Haskell and Perl to see what operators don’t fit in that bucket.)</div><div><br class=""></div><div><br class=""></div><div>I guess I’d summarize my stance as “this proposal enshrines our current problems with operator semantics in order to improve consistency in the syntax” (with “enshrines” meaning “makes harder to change later”), and that doesn’t seem like a good enough reason to change from what we have now.</div><div><br class=""></div><div>(In particular, we don’t have any operator members now, besides in protocols. That means adding them in any form can probably be an additive change, or at least nearly so. Once we do, however, we’d have a hard time switching to a <i class="">different</i>&nbsp;model without breaking everyone’s code, and we should really stop breaking everyone’s code with new language changes at some point.)</div><div><br class=""></div><div>Jordan</div><div><br class=""></div><div>P.S. Thanks for taking my concerns seriously and taking the time to respond to them. :-)</div><div class=""><br class=""></div></body></html>