Operator implementation inside struct/class body


(Vanderlei Martinelli) #1

Since the first public betas I’d like to know why operator implementation
have to be written outside the body of its owner.

Take as example the code:

protocol MyEquatable {

    @warn_unused_result

    func ==(lhs: Self, rhs: Self) -> Bool

}

struct MyStruct: MyEquatable {

    let foo: String

    let bar: String

}

func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {

    return lhs.foo == rhs.foo && lhs.bar == rhs.bar

}

Why we cannot write:

protocol MyEquatable {

    @warn_unused_result

    func ==(lhs: Self, rhs: Self) -> Bool

}

struct MyStruct: MyEquatable {

    let foo: String

    let bar: String

    func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {

        return lhs.foo == rhs.foo && lhs.bar == rhs.bar

    }

}

Any thoughts?

-Van


(Chris Lattner) #2

Since the first public betas I’d like to know why operator implementation have to be written outside the body of its owner.

Yep, this is a generally desirable feature (at least for symmetric operators). This would also be great to get dynamic dispatch of operators within class declarations. I don’t think we have a firm proposal nailing down how name lookup works with this though.

-Chris

···

On Jan 30, 2016, at 9:03 PM, Vanderlei Martinelli via swift-evolution <swift-evolution@swift.org> wrote:

Take as example the code:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
}

func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
    return lhs.foo == rhs.foo && lhs.bar == rhs.bar
}

Why we cannot write:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
    
    func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
        return lhs.foo == rhs.foo && lhs.bar == rhs.bar
    }

}

Any thoughts?

-Van

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Haravikk) #3

Definitely a +1 from me for the feature.

What are the name lookup issues? Do you mean cases where an operator for Foo == Foo exists in more than one location? Personally I’d just stick with what we have now, i.e- treat operator implementations within a specific class/struct as being globally defined anyway and throw an error if the same signature is declared more than once.

One minor issue around putting them in class/struct bodies though is that I wonder if perhaps a keyword other than func should be used? While they are functions, they aren’t methods of instances. At the very least they should probably need to be static.

···

On 31 Jan 2016, at 05:26, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 30, 2016, at 9:03 PM, Vanderlei Martinelli via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Since the first public betas I’d like to know why operator implementation have to be written outside the body of its owner.

Yep, this is a generally desirable feature (at least for symmetric operators). This would also be great to get dynamic dispatch of operators within class declarations. I don’t think we have a firm proposal nailing down how name lookup works with this though.

-Chris

Take as example the code:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
}

func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
    return lhs.foo == rhs.foo && lhs.bar == rhs.bar
}

Why we cannot write:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
    
    func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
        return lhs.foo == rhs.foo && lhs.bar == rhs.bar
    }

}

Any thoughts?

-Van

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charles Srstka) #4

+1. I’ve been meaning to write up something about this for some time. The thing I’d change is to include only the right-hand side as an argument, and use ‘self’ to refer to the left-hand side:

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
    
    func ==(other: MyStruct) -> Bool {
        return self.foo == other.foo && self.bar == other.bar
    }
}

Charles

···

On Jan 30, 2016, at 11:03 PM, Vanderlei Martinelli via swift-evolution <swift-evolution@swift.org> wrote:

Since the first public betas I’d like to know why operator implementation have to be written outside the body of its owner.

Take as example the code:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
}

func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
    return lhs.foo == rhs.foo && lhs.bar == rhs.bar
}

Why we cannot write:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
    
    func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
        return lhs.foo == rhs.foo && lhs.bar == rhs.bar
    }

}

Any thoughts?

-Van


(Vanderlei Martinelli) #5

Good idea! For the syntax I suggest something like this:

protocol MyEquatable {
    @warn_unused_result
    operator ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String

    operator ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
        return lhs.foo == rhs.foo && lhs.bar == rhs.bar
    }
}

`operator` meaning `static func`

-Van

···

On Sun, Jan 31, 2016 at 11:14 AM, Haravikk <e-mail@haravikk.me> wrote:

Definitely a +1 from me for the feature.

What are the name lookup issues? Do you mean cases where an operator for
Foo == Foo exists in more than one location? Personally I’d just stick with
what we have now, i.e- treat operator implementations within a specific
class/struct as being globally defined anyway and throw an error if the
same signature is declared more than once.

One minor issue around putting them in class/struct bodies though is that
I wonder if perhaps a keyword other than func should be used? While they
are functions, they aren’t methods of instances. At the very least they
should probably need to be static.

On 31 Jan 2016, at 05:26, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

On Jan 30, 2016, at 9:03 PM, Vanderlei Martinelli via swift-evolution < > swift-evolution@swift.org> wrote:

Since the first public betas I’d like to know why operator implementation
have to be written outside the body of its owner.

Yep, this is a generally desirable feature (at least for symmetric
operators). This would also be great to get dynamic dispatch of operators
within class declarations. I don’t think we have a firm proposal nailing
down how name lookup works with this though.

-Chris

Take as example the code:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String
}

func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
    return lhs.foo == rhs.foo && lhs.bar == rhs.bar
}

Why we cannot write:

protocol MyEquatable {
    @warn_unused_result
    func ==(lhs: Self, rhs: Self) -> Bool
}

struct MyStruct: MyEquatable {
    let foo: String
    let bar: String

    func ==(lhs: MyStruct, rhs: MyStruct) -> Bool {
        return lhs.foo == rhs.foo && lhs.bar == rhs.bar
    }

}

Any thoughts?

-Van

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Chris Lattner) #6

Definitely a +1 from me for the feature.

What are the name lookup issues? Do you mean cases where an operator for Foo == Foo exists in more than one location?

Yes. Name lookup has to have a well defined search order, which defines shadowing and invalid multiple definition rules.

Personally I’d just stick with what we have now, i.e- treat operator implementations within a specific class/struct as being globally defined anyway and throw an error if the same signature is declared more than once.

We need multiple modules to be able to define instances of an operator, we need operators in extensions, and we need retroactive conformance to work, as with any other member.

One minor issue around putting them in class/struct bodies though is that I wonder if perhaps a keyword other than func should be used? While they are functions, they aren’t methods of instances. At the very least they should probably need to be static.

As we do with protocols, I think we should continue to treat “func <operatoridentifier>” specially. That said, I’d welcome other people’s thoughts on this. Would it be more clear to have:

protocol P {
  operator ==(a : Self, b : Self) -> Bool
}

Does “operator” buy us anything there over func? If we went this direction, it would require taking operator as a keyword.

-Chris

···

On Jan 31, 2016, at 5:14 AM, Haravikk <e-mail@haravikk.me> wrote:


(Goffredo Marocchi) #7

Comments inline.

Definitely a +1 from me for the feature.

What are the name lookup issues? Do you mean cases where an operator for Foo == Foo exists in more than one location?

Yes. Name lookup has to have a well defined search order, which defines shadowing and invalid multiple definition rules.

Personally I’d just stick with what we have now, i.e- treat operator implementations within a specific class/struct as being globally defined anyway and throw an error if the same signature is declared more than once.

We need multiple modules to be able to define instances of an operator, we need operators in extensions, and we need retroactive conformance to work, as with any other member.

One minor issue around putting them in class/struct bodies though is that I wonder if perhaps a keyword other than func should be used? While they are functions, they aren’t methods of instances. At the very least they should probably need to be static.

As we do with protocols, I think we should continue to treat “func <operatoridentifier>” specially. That said, I’d welcome other people’s thoughts on this. Would it be more clear to have:

protocol P {
operator ==(a : Self, b : Self) -> Bool
}

Does “operator” buy us anything there over func? If we went this direction, it would require taking operator as a keyword.

If it can help bring the feature in more painlessly then I think it is a worthy sacrifice in terms of reserving it as a keyword. It is not uncommon to see operator overloading taken as a special case anyways.

···

Sent from my iPhone

On 1 Feb 2016, at 04:55, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 31, 2016, at 5:14 AM, Haravikk <e-mail@haravikk.me> wrote:

-Chris
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Haravikk) #8

Why? Like I say, the operator itself is really static, not a method of an instance, as it declares what it operates on in its parameters, just as an operator does now, the only difference is being able to declare it in class/struct for clarity. There’s also no talk of making explicit operator declarations such as Foo.==(a, b), so it seems to me that nothing will actually change functionally from the way operators are now. A class/struct conforms to an operator requirement if an operator with the appropriate signature exists.

We only need to consider shadowing etc. if we want to instead change operators to be instance methods where the operation is applied to self plus any other optional values, e.g-

  class Foo {
    func ==(rhs:Foo) {
      return self.someProperty == rhs.someProperty
    }
  }

But that would require operator declarations to be completely redesigned, as opposed to the proposal which simply asks for something like:

  class Foo {
    static func ==(lhs: Foo, rhs:Foo) {
      return lhs.someProperty == rhs.someProperty
    }
  }

Which is essentially identical to the things are now, only the declaration happens to be grouped with a type, rather than declared globally like we have now.

That said, this does raise an interesting point, as in the second example there’s nothing stopping you from declaring an operator in type Foo that has no relevance to it whatsoever (e.g- an operator involving IntegerTypes). So redesigning into instance methods may be desirable as that way the implicit parameter (self) is definitely relevant to the type where the operator is implemented. In that case yes, it would probably need to use the same rules for resolving shadowing/overrides, treating the implicit parameter as the target of the operation (so usually the value on the left, except for prefix operators).

···

On 1 Feb 2016, at 04:55, Chris Lattner <clattner@apple.com> wrote:

We need multiple modules to be able to define instances of an operator, we need operators in extensions, and we need retroactive conformance to work, as with any other member.


(Chris Lattner) #9

There is no semantic difference between the two, it is an aesthetic tradeoff.

-Chris

···

On Jan 31, 2016, at 11:38 PM, Goffredo Marocchi <panajev@gmail.com> wrote:

As we do with protocols, I think we should continue to treat “func <operatoridentifier>” specially. That said, I’d welcome other people’s thoughts on this. Would it be more clear to have:

protocol P {
operator ==(a : Self, b : Self) -> Bool
}

Does “operator” buy us anything there over func? If we went this direction, it would require taking operator as a keyword.

If it can help bring the feature in more painlessly then I think it is a worthy sacrifice in terms of reserving it as a keyword. It is not uncommon to see operator overloading taken as a special case anyways.


(Chris Lattner) #10

We need multiple modules to be able to define instances of an operator, we need operators in extensions, and we need retroactive conformance to work, as with any other member.

Why?

I’m not sure which piece you’re asking about. I’m just saying that name lookup needs to follow an algorithm, and that algorithm needs to be specified.

Like I say, the operator itself is really static, not a method of an instance, as it declares what it operates on in its parameters, just as an operator does now, the only difference is being able to declare it in class/struct for clarity. There’s also no talk of making explicit operator declarations such as Foo.==(a, b),

I agree.

so it seems to me that nothing will actually change functionally from the way operators are now.

You need to specify the lookup order, and how to determine whether something is a shadow or redefinitions. The compiler needs to have an algorithm to decide this, and this is an important part of the behavior of the feature. As such a proposal should specify how it works.

-Chris

···

On Feb 1, 2016, at 1:47 AM, Haravikk <swift-evolution@haravikk.me> wrote:

On 1 Feb 2016, at 04:55, Chris Lattner <clattner@apple.com> wrote:


(Vanderlei Martinelli) #11

The initial proposal mentions static members, because I thought it was only
a matter of moving things from one place to other and because I saw
something like this in C#.

After reading your messages, however, I have to agree that the second
approach, using instances members, is a much better one.

About the name lookup algorithm and shadow/override things, I’m afraid I do
not know what to say. What options do we have?

Regards,

-Van

···

On Mon, Feb 1, 2016 at 6:41 PM, Chris Lattner <clattner@apple.com> wrote:

> On Feb 1, 2016, at 1:47 AM, Haravikk <swift-evolution@haravikk.me> > wrote:
>
>
>> On 1 Feb 2016, at 04:55, Chris Lattner <clattner@apple.com> wrote:
>>
>> We need multiple modules to be able to define instances of an operator,
we need operators in extensions, and we need retroactive conformance to
work, as with any other member.
>
> Why?

I’m not sure which piece you’re asking about. I’m just saying that name
lookup needs to follow an algorithm, and that algorithm needs to be
specified.

> Like I say, the operator itself is really static, not a method of an
instance, as it declares what it operates on in its parameters, just as an
operator does now, the only difference is being able to declare it in
class/struct for clarity. There’s also no talk of making explicit operator
declarations such as Foo.==(a, b),

I agree.

> so it seems to me that nothing will actually change functionally from
the way operators are now.

You need to specify the lookup order, and how to determine whether
something is a shadow or redefinitions. The compiler needs to have an
algorithm to decide this, and this is an important part of the behavior of
the feature. As such a proposal should specify how it works.

-Chris


(Jessy) #12

After reading your messages, however, I have to agree that the second approach, using instances members, is a much better one.

Agreed. I think C# only uses static functions because it doesn’t have module-scope operators like Swift does.

Here’s what instance operators would look like with shorthand argument names:

struct Vector2 {
   func * (Self) -> Self {
      return Self(x * $0.x, y * $0.y)
   }
}

While I believe that instance operators would represent the vast majority of operations, operators that use operands of different types don’t make sense to be defined in a type. I love that Swift allows that sort of thing at module scope.

func * (matrix: Matrix2x2, vector: Vector2) -> Vector2 {…

I bet it makes sense to enforce that an operator is only allowed at module scope if its operands aren’t of the same type.


(Haravikk) #13

Would it even need to be that complex? I would say to just allow global scope operators as well, and leave it to the developer to decide if that’s the best place to put it versus putting it into a type.

That said, I’m finding it hard to think of examples that really need to be in the global scope, as your example seems to me like it still makes the most sense to be placed within the Matrix2x2 type like so:

  func * (vector:Vector2) -> Vector2 { // self is matrix2x2 }

Since the left hand side is still the focal point of this operator. The only case I could think of that might still require a global operator would be one that can handle arguments of type Any.

···

On 8 Feb 2016, at 22:02, Jessy Catterwaul via swift-evolution <swift-evolution@swift.org> wrote:

While I believe that instance operators would represent the vast majority of operations, operators that use operands of different types don’t make sense to be defined in a type. I love that Swift allows that sort of thing at module scope.

func * (matrix: Matrix2x2, vector: Vector2) -> Vector2 {…

I bet it makes sense to enforce that an operator is only allowed at module scope if its operands aren’t of the same type.


(Jessy) #14

That said, I’m finding it hard to think of examples that really need to be in the global scope, as your example seems to me like it still makes the most sense to be placed within the Matrix2x2 type like so:

  func * (vector:Vector2) -> Vector2 { // self is matrix2x2 }

Since the left hand side is still the focal point of this operator. The only case I could think of that might still require a global operator would be one that can handle arguments of type Any.

Good point! I haven’t been exposed to that approach or thought of it. That might be the best way to handle things, even when operators are allowed to take more than one operand without resorting to passing them in as a tuple like we must now.