[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