[swift-evolution] [Proposal] Instance Operators

Sean Heber sean at fifthace.com
Fri Feb 26 10:55:03 CST 2016


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).

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.
> 
>> On Feb 25, 2016, at 1:59 PM, Vanderlei Martinelli via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> Hello.
>> 
>> The proposal can be also read at https://gist.github.com/vmartinelli/67d6ad234c7a4e14f8d5
>> 
>> Original thread: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160125/008508.html
>> 
>> Opinions, comments and corrections (including on English grammar) are all welcome. :-)
>> 
>> -Van
>> 
>> ---------
>> 
>> Instance Operators
>> 
>> 	• Proposal: SE-NNNN
>> 	• Author: Vanderlei Martinelli
>> 	• Status: Awaiting review
>> 	• Review manager: TBD
>> Introduction
>> 
>> The proposal aims to move operator implementation from the global and static scope into extension/struct/class instance scope.
>> 
>> Swift-evolution thread: link to the discussion thread for that proposal
>> 
>> Motivation
>> 
>> When writing the protocol interface the operator is declarated inside the scope of that protocol, but its implementation has to be static and global. This, besides being inconsistent, might not the behaviour expected by programmers coming from other languages that have some kind of support for interface/protocol and operator implementation.
>> 
>> Example:
>> 
>> // MARK: - protocol
>> 
>> 
>> 
>> public protocol
>>  MyDoubleType {
>>     
>> public func someUsefulFunction
>> ()
>> 
>>     
>> public func *(lhs: Self, rhs: Self) ->
>>  Self
>>     
>> public postfix func ++(inout x: Self) ->
>>  Self
>> }
>> 
>> 
>> // MARK: - implementation
>> 
>> 
>> 
>> extension Double
>> : MyDoubleType {
>> 
>>     
>> public func someUsefulFunction
>> () {
>>         
>> // ...
>> 
>>     }
>> 
>>     
>> // we cannot implement the operators here...
>> 
>> 
>> }
>> 
>> 
>> // ... but have to implement them here
>> 
>> 
>> 
>> public func *(lhs: Double, rhs: Double) -> Double
>>  {
>>     
>> return lhs.
>> multipliedBy(rhs)
>> }
>> 
>> 
>> public postfix func ++(inout x: Double) -> Double
>>  {
>>     x 
>> += 1.0
>> 
>>     
>> return
>>  x
>> }
>> 
>> Also the current implementation does not leave much room for future expansion in the use of operators (such as conversion between values, for example).
>> 
>> Proposed solution
>> 
>> Move the operator implementation into the extension/struct/class scope and turn operator funcs into instance funcs, using the operator keyword.
>> 
>> Detailed design
>> 
>> Protocol conformance
>> 
>> After the change the above code can be written like the example bellow.
>> 
>> // MARK: - protocol
>> 
>> 
>> 
>> public protocol
>>  MyDoubleType {
>>     
>> public func someUsefulFunction
>> ()
>> 
>>     
>> public operator *(rhs: Self) -> Self
>> 
>>     
>> public mutating postfix operator ++() -> Self
>> 
>> }
>> 
>> 
>> // MARK: - implementation
>> 
>> 
>> 
>> extension Double
>> : MyDoubleType {
>> 
>>     
>> public func someUsefulFunction
>> () {
>>         
>> // ...
>> 
>>     }
>> 
>>     
>> public operator *(rhs: Double) -> Double
>>  {
>>         
>> return self.
>> multipliedBy(rhs)
>>     }
>> 
>>     
>> public mutating postfix operator ++() -> Double
>>  {
>>        
>> self += 1.0
>> 
>>        
>> return self
>> 
>>     }
>> 
>> }
>> 
>> Operator funcs everywhere
>> 
>> An operator does not have to be implemented only to conform to a protocol, however. It can be also be implemented in any other place where a common func is. This means that even the current form can be supported.
>> 
>> Operator internal names
>> 
>> Perhaps because of the internal implementation of Swift, operators have to have names to be handled. The suggestion is to adopt __operator__GreaterThanOrEqual for a >= operator, as example. The operator introduction would be:
>> 
>> infix operator >=
>>  {
>>     
>> associativity none
>> 
>>     
>> precedence 130
>> 
>>     name 
>> "GreaterThanOrEqual"
>> 
>> }
>> 
>> So the code will be written like this...
>> 
>> struct
>>  MyStruct {
>>     
>> operator >=(other: MyStruct) -> Bool
>>  {
>>         
>> return ...
>> 
>>     }
>> }
>> 
>> ... but translated internally to this:
>> 
>> struct
>>  MyStruct {
>>     
>> func __operator__GreaterThanOrEqual(other: MyStruct) -> Bool
>>  {
>>         
>> return ...
>> 
>>     }
>> }
>> 
>> Impact on existing code
>> 
>> Since after this change an operator can be implemented in any other place where a common func can be, the current implementation may continue to exist, but marked as deprecated with a compiler/analyser warning.
>> 
>> Also the func keyword would be deprecated for operators as well, using the operator to declare/implement an operator func.
>> 
>> Alternatives considered
>> 
>> Status quo
>> 
>> Leave things as they are. Even being inconsistent or not allowing new possibilities that instance operators will bring.
>> 
>> Static implementation inside extension/struct/class scope
>> 
>> This is the way operators are implemented in C#, for example. The change would be only aesthetic. The functionality would remain the same as today.
>> 
>> As the types may differ from protocol/structure/class, this would allow state within the scope of operators that have nothing to do with that type. Not a good thing. In this case it might be better to keep things as they are.
>> 
>> Example:
>> 
>> // MARK: - protocol
>> 
>> 
>> 
>> public protocol
>>  MyDoubleType {
>>     
>> public func someUsefulFunction
>> ()
>> 
>>     
>> public static operator *(lhs: Self, rhs: Self) -> Self
>> 
>>     
>> public static operator /(lhs: Int64, rhs: Int64) -> Int64 // what?
>> 
>>     
>> public static postfix operator ++(inout x: Self) -> Self
>> 
>> }
>> 
>> 
>> // MARK: - implementation
>> 
>> 
>> 
>> extension Double
>> : MyDoubleType {
>> 
>>     
>> public func someUsefulFunction
>> () {
>>         
>> // ...
>> 
>>     }
>> 
>>     
>> public static operator *(lhs: Double, rhs: Double) -> Double
>>  {
>>         
>> return lhs.
>> multipliedBy(rhs)
>>     }
>> 
>>     
>> // this should be implemented inside a Int64 type, not here...
>> 
>>     
>> public static operator /(lhs: Int64, rhs: Int64) -> Int64
>>  {
>>         
>> // ...
>> 
>>     }
>> 
>>     
>> public static postfix operator ++(inout x: Double) -> Double
>>  {
>>         x 
>> += 1.0
>> 
>>         
>> return
>>  x
>>     }
>> 
>> }
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list