<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=""><div class="">Proposal link:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span><a href="https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md" class="">https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md</a></div><div class=""><br class=""></div>Hi all,<div class=""><br class=""></div><div class=""><div><blockquote type="cite" class=""><div class="">On May 17, 2016, at 8:33 PM, Chris Lattner &lt;<a href="mailto:clattner@apple.com" class="">clattner@apple.com</a>&gt; wrote:</div><div class=""><div class="">The review of "SE-0091: Improving operator requirements in protocols" begins now and runs through May 23. The proposal is available here:<br class=""></div></div></blockquote><div><br class=""></div><div>My apologies for being very, very late with this review. As has been noted elsewhere, the core team has been rather DoS’d for the last few weeks, and even very important things are getting lost in the shuffle.</div><div><br class=""></div><div>I support the goals of this proposal, but I’m strongly against the approach it takes because it is fundamentally based on forwarding functions in the global scope.</div><div><br class=""></div><div><b class="">My Complaints with the Proposal</b></div><div>1) Adding an operator function to a type doesn’t “just work”, which is surprising. Let’s do something silly and add a ‘*’ operator to repeat strings:</div><div><br class=""></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><div><div><font face="Menlo" class="">extension String {</font></div></div></div><div class=""><div><div><font face="Menlo" class="">&nbsp; static func *(lhs: String, rhs: Int) -&gt; String { … }</font></div></div></div><div class=""><div><div><font face="Menlo" class="">}</font></div></div></div></blockquote><div class=""><div><div><br class=""></div><div>If I try to use this in the obvious way</div><div><br class=""></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><div><div><font face="Menlo" class="">print(“hello” * 3)</font></div></div></div></blockquote><div class=""><div><div><br class=""></div><div>I will get a compiler error. I have two paths at this point, neither of which is obvious: either I need to find (or author!) a protocol to conform to that allows my particular brand of ‘*’ to match and has a global forwarding operator, or I need to implement the operator at the global scope:</div><div><br class=""></div><div><div class=""><div><div><font face="Menlo" class="">&nbsp; func *(lhs: String, rhs: Int) -&gt; String { ... }</font></div></div></div><div class=""><div></div></div></div><div><br class=""></div><div>2) Creating a new operator now requires more boilerplate:</div><div><span class="Apple-tab-span" style="white-space:pre">        </span>a) An operator definition, e.g.,</div><div><br class=""></div></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><div><div><font face="Menlo" class="">infix operator +++ { }</font></div></div></div></blockquote><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>b) A protocol that describes this new operator,</div><div class=""><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><font face="Menlo" class="">protocol Concatable {</font></div><div class=""><font face="Menlo" class="">&nbsp; func operator+++(lhs: Self, rhs: Self) -&gt; Self</font></div><div class=""><font face="Menlo" class="">}</font></div></blockquote><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>c) A forwarding operator function based on that protocol</div><div class=""><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><font face="Menlo" class="">func operator+++&lt;T : Concatable&gt;(lhs: T, rhs: T) -&gt; T {</font></div><div class=""><font face="Menlo" class="">&nbsp; return T.+++(lhs, rhs)</font></div><div class=""><font face="Menlo" class="">}</font></div></blockquote><div class=""><br class=""></div><div class="">Yes, creating a new operator shouldn’t be the easiest thing in the world, but that is a ton of boilerplate. Moreover…</div><div class=""><br class=""></div><div class="">3) The protocols used to describe these operators aren’t really natural: they are bare-bones, purely-syntactic protocols that have no meaning other than to do forwarding. Just putting “+” into Arithmetic isn’t good enough: we’ll need another one for Strideable, and we’ll almost surely end up with a “HasBinaryPlus” protocol like this:</div><div class=""><br class=""></div><div class=""><blockquote style="margin: 0px 0px 0px 40px; border: none; padding: 0px;" class=""><div class=""><font face="Menlo" class="">protocol HasBinaryPlus {</font></div><div class=""><font face="Menlo" class="">&nbsp; func operator+(lhs: Self, rhs: Self) -&gt; Self</font></div><div class=""><font face="Menlo" class="">}</font></div><div class=""><font face="Menlo" class=""><br class=""></font></div></blockquote></div><div class="">so that other non-arithmetic types that want to introduce a binary plus with this form can opt to the protocol rather than having to write the forwarding function I complained about in (1). Moreover, Arithmetic will inherit this HasBinaryPlus. Scale that out and you have Arithmetic being composed of a pile of meaningless syntactic protocols: HasBinaryPlus, HasBinaryMinus, HasBinaryStar, HasBinarySlash, HasPrefixPlus, HasPrefixMinus. It makes Arithmetic confusing because the requirements are scattered.</div><div class=""><br class=""></div><div class="">It’s not even that there is just one protocol per operator, either: even just with the standard library, + will have at least two protocols associated with it: HasBinaryPlus and Strideable to cover the various cases in the standard library. It’s probably not enough, and there will surely be more protocols created for binary + simply to provide the forwarding functions.</div><div class=""><br class=""></div><div class="">4) The rule prohibiting operator functions defined in a type that don’t conform to a protocol limits retroactive modeling. If you don’t have a protocol in hand, you have to use a global operator.</div><div class=""><br class=""></div><div class="">5) Forwarding functions aren’t good for tools. Under this proposal, if I write “1 + 2” and use a tool to look at which “+” it resolved to, what will we see? The generic forwarding operator. Even though I could look in the source and see this:</div><div class=""><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><font face="Menlo" class="">extension Int {</font></div><div class=""><font face="Menlo" class="">&nbsp; static func +(lhs: Int, rhs: Int) -&gt; Int { … }</font></div><div class=""><font face="Menlo" class="">}</font></div></blockquote><div class=""><br class=""></div><div class="">and even those that’s what will get called, my tools aren’t going to interpret the body of the global forwarding function for + to resolve it in the obvious way.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><b class="">The Good Parts</b></div><div class=""><b class=""><br class=""></b></div><div class="">With all that negative, one might get the impression that I don’t like operators in types. I think there are improvements here:</div><div class=""><br class=""></div><div class=""><div class="">I) Writing an operator function in a type/extension of a type is far more natural that writing one at global scope for the common case. Even if you’re not planning on conforming to a protocol, it just feels right that (say) String + String should be defined in an extension of String. It’s better for tooling (which can more easily associate the operator + with the String type), code organization, works with the new meaning of the “private” access modifier, and simply feels like Swift.</div></div><div class=""><br class=""></div><div class="">II) The requirement to use “static” on the operator function requirement in the protocol makes perfect sense to me. It’s a lot clearer, and communicates the semantics better. I can’t recall why we didn’t do this in the first place.</div><div class=""><br class=""></div><div class="">III) The goal to reduce the total number of overloads is laudable. It can help type checker performance (fewer overloads == less exponential behavior) and improve diagnostics (fewer candidates to display on error). The key insight here is that we don’t want to consider both a generic operator based on some protocol (e.g., + for Arithmetic types) <i class="">and</i>&nbsp;the operator functions that are used to satisfy the corresponding requirement.</div><div class=""><br class=""></div><div class=""><b class="">An Alternative Approach</b></div><div class=""><br class=""></div><div class="">Let’s accept (I) and (II). But, let’s make operator lookup always be global, so that it sees all operators defined at either module scope or within a type/extension of a type. This gives us the syntactic improvements of the SE-0091 “immediately”, and eliminates all five of my complaints above: the natural Swift thing of defining your functionality within the type or an extension thereof “just works”. It’s weird in the sense that operators will be the only place where we do such global lookup—finding entries at both global and type scope. However, SE-0091 is introducing a different weird name lookup rule, and it feels like there’s really no way to avoid it: we simply don’t want normal lexical name lookup for operators when they can be defined in types.</div><div class=""><br class=""></div><div class="">This approach does not (directly) give any of the type checker performance/QoI improvements of (III). However, we can achieve that by making the key insight of (III) part of the semantic model: when we find all operators, we also find the operators in the protocols themselves. The operators in the protocols are naturally generic, e.g., the Arithmetic + effectively has a generic function type like this:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>&lt;Self: Arithmetic&gt; (Self, Self) -&gt; Self</div><div class=""><br class=""></div><div class="">which is basically what the forwarding functions look like in SE-0091 at a type level. Then, we say that we do not consider an operator function if it implements a protocol requirement, because the requirement is a generalization of all of the operator functions that satisfy that requirement. With this rule, we’re effectively getting the same effects of SE-0091’s approach to (III)—but it’s more automatic.</div><div class=""><br class=""></div><div class="">Note that this approach could change semantics. When you type-check a generic function, you’re inferring the generic type arguments. That could end up type checking differently than considering a more specific function. For example, let’s say we were allowed to fulfill an operator function requirement with an operator function that was a subtype of the requirement (a commonly-requested feature), e.g.,</div><div class=""><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><font face="Menlo" class="">class Super { }</font></div><div class=""><font face="Menlo" class=""><br class=""></font></div><div class=""><font face="Menlo" class="">class Sub : Super, Equatable {</font></div><div class=""><font face="Menlo" class="">&nbsp; static func ==(lhs: Super, rhs: Super) -&gt; Bool { … } // note: currently ill-formed, but requested often</font></div><div class=""><font face="Menlo" class="">}</font></div><div class=""><font face="Menlo" class=""><br class=""></font></div><div class=""><font face="Menlo" class="">func testMe(sup: Super) -&gt; Bool {</font></div><div class=""><font face="Menlo" class="">&nbsp; return sup == sup &nbsp;// error: Equatable.== fails because&nbsp;“Super” is not equatable,</font></div><div class=""><font face="Menlo" class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// and Sub.== isn’t considered because it satisfies the Equatable.== requirement</font></div><div class=""><font face="Menlo" class="">}</font></div></blockquote><div class=""><br class=""></div><div class="">I suspect this is acceptable. If we ever did start to allow one to satisfy a requirement with something that is a subtype, perhaps we just wouldn’t extend that rule to operator function requirements. Note that it’s possible that you can trigger this in the current type system as well—I haven’t tried.</div><div class=""><br class=""></div><div class="">One could experiment with this solution just with the standard library: take away all of the concrete +’s and map them to “Arithmetic.add” or “Strideable.add” to get down to the minimal set, and then put the forwarding functions in to see how well the type checker copes with it (e.g., performance, diagnostics, what unexpected breakage do we see). There’s a way to push the experiment further—by teaching the type checker to do this pruning rule, which doesn’t actually depend on introducing the ability to define an operator function within a type—but of course that requires more implementation effort.</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>- Doug</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div></body></html>