<div dir="ltr">I'm +1 on Nicola's suggestions.<div><br></div><div>Basically, remove operators from protocols, simplifying the language. This doesn't prevent operators from using protocols (through generics).</div><div><br></div><div>Operators with protocols can still be defined as Nicola suggested, through a regular protocol method and a generic. Symmetrical operators can use a static method on the protocol if that's desirable. Swift has all the functionality for this already.</div><div><br></div><div>It also doesn't have the downside of preventing operators from working on things like types, tuples, etc.</div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Mon, Feb 29, 2016 at 5:24 PM, 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"><span class=""><br>
> Am 26.02.2016 um 17:55 schrieb Sean Heber via swift-evolution <<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>>:<br>
><br>
> I agree with all of this. I’m not sure operators really belong *inside* of the type. IMO, attempting to include them within the type is only desirable because of the way they are declared inside of the protocol. I think the asymmetry should be addressed on that side instead (if at all).<br>
<br>
</span>Good point! Maybe we should think about turning operators into multi-methods which are dynamically dispatched on all arguments like Dylan had. Or at least just move their declaration out of protocols.<br>
<br>
-Thorsten<br>
<div class="HOEnZb"><div class="h5"><br>
><br>
> l8r<br>
> Sean<br>
><br>
><br>
><br>
>> On Feb 26, 2016, at 10:43 AM, plx via swift-evolution <<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>> wrote:<br>
>><br>
>> After careful consideration, I am not sure this sort of thing is actually a change that would *actually* be an obvious improvement.<br>
>><br>
>> # First Concern: Code-Organization/Ownership Issues<br>
>><br>
>> Although it is easy to see how to handle operators that have *homogenous* types — e.g. anything like `func +(lhs: T, rhs: T) -> T` — it’s really unclear how a proposal like this *should* work for operators that have *heterogeneous* types — e.g. anything like `func *(lhs: T, rhs: U) -> U ` (etc.).<br>
>><br>
>> Since we’re talking about operators, this isn’t really a hypothetical concern, either:<br>
>><br>
>> ## Example: Vector/Matrix Operations<br>
>><br>
>> func *(lhs: Matrix4x4, rhs: Vector4) -> Vector4<br>
>> func *(lhs: Vector4, rhs: Matrix4x4) -> Vector4<br>
>><br>
>> Both operations are reasonable to define, but defining the operator as instance methods seems to leave you in a rather awkward spot:<br>
>><br>
>> - perhaps one is implemented by `Matrix4x4` and the other by `Vector4` (really odd code organization imho…)<br>
>> - perhaps both are implemented by, say, `Matrix4x4`, but one of them is using nonstandard syntax (defeating the point of custom operators, imho)<br>
>> - perhaps the proposal lets an operator-function declaration indicate *which* argument is `self` (new syntax/new semantics)<br>
>><br>
>> …whereas the “operators are static functions” approach makes it reasonable to have both versions defined at the same scope (and e.g. "near each other”).<br>
>><br>
>> I know the specific proposal here wouldn’t eliminate the ability to define the operators as currently, but it’d be a shame to be unable to include things like the above method in protocols.<br>
>><br>
>> ## Example: Scalar/Vector Operations<br>
>><br>
>> Similarly, at a more-basic level, you might *want* this:<br>
>><br>
>> protocol VectorType : Equatable {<br>
>> typealias Component : NumericType // if it existed<br>
>><br>
>> // to match convention, scalars go *in front* of vectors<br>
>> operator *(lhs: Int, rhs: Self) -> Self<br>
>> operator *(lhs: Self.Component, rhs: Self) -> Self<br>
>><br>
>> // but why should we not be flexible on this point?<br>
>> operator *(lhs: Self, rhs: Int) -> Self<br>
>> operator *(lhs: Self, rhs: Self.Component) -> Self<br>
>> }<br>
>><br>
>> …and are we going to make `struct Vector4`’s conformance to `VectorType` contingent on the presence of extension methods on `Int` (etc.)?<br>
>><br>
>> That just seems really unintuitive and counterproductive.<br>
>><br>
>> # Second Concern: Dynamic Operator Dispatch Not Really Necessary<br>
>><br>
>> What I mean is, in the use cases that come to mind for dynamic operator-dispatch. making operators dynamically-dispatched wouldnt’ actually provide any benefit over what you can achieve today with protocols.<br>
>><br>
>> EG, for `==`, consider a protocol and operators like below:<br>
>><br>
>> protocol DynamicEquatable : class {<br>
>> func isEqual(other: Self) -> Bool<br>
>> }<br>
>><br>
>> func ==<T:DynamicEquatable>(lhs: T, rhs: T) -> Bool {<br>
>> return (lhs === rhs) || lhs.isEqual(rhs)<br>
>> }<br>
>><br>
>> func !=<T:DynamicEquatable>(lhs: T, rhs: T) -> Bool {<br>
>> return (lhs !== rhs) && !lhs.isEqual(rhs)<br>
>> }<br>
>><br>
>> …which as far as I can tell gets you back to the same place you’d be if you had a dynamically-dispatched `==` (both in terms of *what it would do* and also *what issues it would still have*).<br>
>><br>
>> Is there some (beneficial?) aspect of making `==` dynamically-dispatched that isn’t also present in the above design?<br>
>><br>
>> Are there operators for which there would be a material difference between the operator being dynamically-dispatched and the operator being defined over a protocol that has a dynamically-dispatched method providing the implementation?<br>
>><br>
>> # Remark<br>
>><br>
>> For `==` in particular, you could *maybe* improve the above slightly if Swift had a way to write a where clause like `U:>T` — meaning “U is a subclass of T, but not T itself” — as then you could add variants like:<br>
>><br>
>> func ==<T:DynamicEquatable,U:DynamicEquatable where T:>U>(lhs: T, rhs: U) -> Bool {<br>
>> return (lhs === rhs) || lhs.isEqual(rhs)<br>
>> }<br>
>><br>
>> func ==<T:DynamicEquatable,U:DynamicEquatable where U:>T>(lhs: T, rhs: U) -> Bool {<br>
>> return (lhs === rhs) || rhs.isEqual(lhs)<br>
>> }<br>
>><br>
>> …which would hopefully make a direct call into the more-derived type’s implementation of `isEqual`, which would be more-likely to contain a fast path, but even so it’s not obvious that there’s all that much of an actual win to be had here, in practice.<br>
</div></div><div class="HOEnZb"><div class="h5">_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">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>
</div></div></blockquote></div><br></div>