[swift-evolution] [Proposal] Instance Operators
Thorsten Seitz
tseitz42 at icloud.com
Mon Feb 29 00:24:42 CST 2016
> Am 26.02.2016 um 17:55 schrieb Sean Heber via swift-evolution <swift-evolution at swift.org>:
>
> 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).
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.
-Thorsten
>
> l8r
> Sean
>
>
>
>> On Feb 26, 2016, at 10:43 AM, plx via swift-evolution <swift-evolution at swift.org> wrote:
>>
>> After careful consideration, I am not sure this sort of thing is actually a change that would *actually* be an obvious improvement.
>>
>> # First Concern: Code-Organization/Ownership Issues
>>
>> 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.).
>>
>> Since we’re talking about operators, this isn’t really a hypothetical concern, either:
>>
>> ## Example: Vector/Matrix Operations
>>
>> func *(lhs: Matrix4x4, rhs: Vector4) -> Vector4
>> func *(lhs: Vector4, rhs: Matrix4x4) -> Vector4
>>
>> Both operations are reasonable to define, but defining the operator as instance methods seems to leave you in a rather awkward spot:
>>
>> - perhaps one is implemented by `Matrix4x4` and the other by `Vector4` (really odd code organization imho…)
>> - perhaps both are implemented by, say, `Matrix4x4`, but one of them is using nonstandard syntax (defeating the point of custom operators, imho)
>> - perhaps the proposal lets an operator-function declaration indicate *which* argument is `self` (new syntax/new semantics)
>>
>> …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”).
>>
>> 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.
>>
>> ## Example: Scalar/Vector Operations
>>
>> Similarly, at a more-basic level, you might *want* this:
>>
>> protocol VectorType : Equatable {
>> typealias Component : NumericType // if it existed
>>
>> // to match convention, scalars go *in front* of vectors
>> operator *(lhs: Int, rhs: Self) -> Self
>> operator *(lhs: Self.Component, rhs: Self) -> Self
>>
>> // but why should we not be flexible on this point?
>> operator *(lhs: Self, rhs: Int) -> Self
>> operator *(lhs: Self, rhs: Self.Component) -> Self
>> }
>>
>> …and are we going to make `struct Vector4`’s conformance to `VectorType` contingent on the presence of extension methods on `Int` (etc.)?
>>
>> That just seems really unintuitive and counterproductive.
>>
>> # Second Concern: Dynamic Operator Dispatch Not Really Necessary
>>
>> 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.
>>
>> EG, for `==`, consider a protocol and operators like below:
>>
>> protocol DynamicEquatable : class {
>> func isEqual(other: Self) -> Bool
>> }
>>
>> func ==<T:DynamicEquatable>(lhs: T, rhs: T) -> Bool {
>> return (lhs === rhs) || lhs.isEqual(rhs)
>> }
>>
>> func !=<T:DynamicEquatable>(lhs: T, rhs: T) -> Bool {
>> return (lhs !== rhs) && !lhs.isEqual(rhs)
>> }
>>
>> …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*).
>>
>> Is there some (beneficial?) aspect of making `==` dynamically-dispatched that isn’t also present in the above design?
>>
>> 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?
>>
>> # Remark
>>
>> 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:
>>
>> func ==<T:DynamicEquatable,U:DynamicEquatable where T:>U>(lhs: T, rhs: U) -> Bool {
>> return (lhs === rhs) || lhs.isEqual(rhs)
>> }
>>
>> func ==<T:DynamicEquatable,U:DynamicEquatable where U:>T>(lhs: T, rhs: U) -> Bool {
>> return (lhs === rhs) || rhs.isEqual(lhs)
>> }
>>
>> …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.
More information about the swift-evolution
mailing list