[Review] SE-0091: Improving operator requirements in protocols

>
> How hard would it be to e.g. simply allow something like this:
>
> func ==<T:Equatable>(lhs: T, rhs: T) -> Bool {
> return lhs T.== rhs
> }
>
> …instead of the `T.==(lhs,rhs)` syntax?

Yeah, I've been meaning to suggest that. And 'T.++(x)' and '(x)T.++' for
prefix and postfix, respectfully.

The "fully-qualified operator" syntax was one that was discussed in some of
the original e-mail threads. It was part an earlier draft of the proposal,
but I changed it to the function call syntax because

- visually, it's a bit tricky to parse with the eyes
- I wasn't sure whether it would pose similar problems for the compiler to
parse
- It doesn't solve the (admittedly edge case) issue of not being able to
refer distinctly to prefix/postfix operators that have the same name as a
first-class function without wrapping the invocation in a closure
- Chris Lattner recommended changing it to the function call syntax after a
core team discussion before the proposal PR was accepted, so it's possible
they have more insight into the parsing/implementation issues?

···

On Mon, May 23, 2016 at 9:20 PM David Sweeris via swift-evolution < swift-evolution@swift.org> wrote:

> On May 18, 2016, at 18:07, plx via swift-evolution < > swift-evolution@swift.org> wrote:

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

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md\]

Hi, Tony. Thanks for working on this. I have to say I’m incredibly concerned with this direction, for two main reasons. I’ll try to break them down here. (Sorry for squeaking in at the end of the review period!)

No worries, thanks for the detailed feedback! I've tried to address your concerns inline.

Overrides

People up until now have been fairly unhappy with how operators are statically dispatched and can’t be overridden. We went all the way towards providing == that automatically calls isEqual(_:) on NSObject, but then you provide == for your subclass and it never gets called…no, wait, it does get called when you do a simple test, but not from the actual code that has NSObject as a static type.

This proposal stays in that space: the proposed “trampoline” operator will dispatch based on the static type of the objects, not the dynamic type. Why? Consider using == on an NSURL and an NSString, both statically typed as NSObject. Given the definition of the trampoline from the proposal

func == <T: Equatable>(lhs: T, rhs: T) -> Bool {
  return T.==(lhs, rhs)
}

T can’t possibly be anything but NSObject. (Neither NSURL nor NSString matches the types of both ‘lhs’ and ‘rhs’.) This isn’t a regression from the current model, as you say, but it does make the current model even more surprising, since normally you’d expect methods to be dynamically dispatched.

Setting Objective-C aside for a moment, is this example consistent with Swift's design philosophies about type safety though? Swift doesn't even let you compare types that seem reasonably compatible, like Float and Double. Should we expect users to be able to compare completely distinct types like NSURL and NSString without a little extra work?

If having `==` map to `NSObject.isEqual` is important for Objective-C compatibility, then there's no reason the runtime can't provide the following operator:

func ==(lhs: NSObject, rhs: NSObject) -> Bool {
  return lhs.isEqual(rhs)
}

which should be more specific than the generic one and always get chosen in the context you desire.

The situation is a little better when one of the types being compared has the other type as a base, because then implementing the operators as class methods instead of static methods does the right thing (with the only caveat being you have to cast the arguments down to the correct type).

Here’s an alternate formation of the trampoline that’s a little better about this…

func == <T: Equatable>(lhs: T, rhs: T) -> Bool {
  return lhs.dynamicType.==(lhs, rhs)
}

…but I’m still not convinced. (People are especially likely to get this wrong without the trampolines being synthesized.)

Yeah, I don’t think arbitrary symmetric operators do work generally across object types. Equality is special since there’s a reasonable default, but that’s probably not the common case. That does make this a little better—if you never have to override an operator function, the static dispatch doesn’t matter.

I’m still concerned about asymmetric operators, however. That includes assignment operators, but also things like operators for chaining tasks, DSLs for constraint systems, etc. It’s not too hard to implement these to forward to something dynamically dispatched (as we’ve both shown), but the default behavior, which currently doesn’t do what you want, will continue not doing what you want.

Assignment Operators

A mutating requirement and a static method with an inout parameter mean different things for a conforming class: the former can only access the class’s properties, while the latter can replace the caller’s reference as well.

class Player { … }

extension Player {
  static func roulette(_ player: inout Player) {
    if randomValue() > 0.1 {
      player.roundsSurvived += 1
    } else {
      // Replace this player…but not any other references to them!
      player = Player()
    }
  }

  /*mutating*/ func solitaire() {
    self.roundsSurvived += 1
    // Cannot replace ‘self’
    //self = Player()
  }
}

I’m not sure if one of these is obviously better than the other (more capable ↔︎ more footgun). I agree with Nicola's point about mutating methods looking better than static methods taking an inout parameter, but that probably shouldn’t be the ultimate deciding factor.

You make a good point here. This is hitting on one of the fundamental problems I've had with mutating requirements in protocols in some of my own projects, which is that even when a class type conforms to such a protocol (or worse, even if the protocol itself is restricted to `class` types only), if I want to use it in a generic context with that protocol as a constraint, I still have to pass it unnecessarily as an inout argument.

On the one hand, fixing that underlying problem might help us here as well. An obvious alternative would be to disallow `inout` on class conformances to assignment operators, but that introduces inconsistency that I'm trying to avoid. I'm not sure if it's any better, either.

Yeah, I’d definitely like to change the behavior we have now. We discussed it previously and decided this was the least bad thing to do, but I think that decision was wrong. That deserves a whole proposal of its own, though.

I know we want to improve type-checker performance, and reducing the number of overloads seems like a way to do that, but I’m not convinced it actually will in a significant way (e.g. “you can now use ten operators in a chain instead of seven” is not enough of a win). It still seems like there ought to be a lot of low-hanging fruit in that area that we could easily clear away, like “an overload containing a struct type will never match any input but that struct type”.

I personally really want to move operators into types, but I want to do it by doing member lookup on the type, and fall back to global operators only if something can’t be found there. That approach

- also has potential to improve type-checker performance
- also removes operators from the global namespace
- also removes the need for “override points” (implementation methods like NSObject.isEqual(_:) and FloatingPoint.isLessThan(_:))

It does privilege the left-hand side of a binary operator, but I think that’s acceptable for the operators we’ve seen in practice. (Of course we would need real data to back that up.)

As Brent pointed out in his reply, without multiple dispatch, you don't really benefit from privileging the lhs argument, and in fact you can end up in situations where the behavior is surprising if you don't implement both orders. For example, in your (NSString, NSURL) example, each class would have to be extended to explicitly support comparison with the other in order to support commutativity if equality was an instance method. I'm not sure that's any better for those particular types than just having the operators global in the first place.

I assume you’d still have to implement it with NSObject as the “other” type, like you do in Objective-C. You’d just return false in that case. But you’d get the correct answer for comparing two NSURLs that are statically typed as NSObject, which you wouldn’t using the vanilla trampoline.

My argument would be that there are still significant enough benefits to move forward:

- for value types and for binary operators that have the same typed arguments, static methods provide a clear benefit w.r.t. consistency (the declaration of the global operator and the static operator look the same) and obvious semantics

Agreed…for operators that go with protocols. Is that all of them? Most of them? I’m not sure.

- for binary operators with differently typed arguments, static operators support commutativity much better than instance methods because both implementations live in the most natural type for that particular case (for example, String + Character and Character + String)

Agreed, but I consider this only a mild plus because extensions are pretty easy in Swift. We put initializers and helper methods on related types all the time.

- if later on we can auto-generate trampolines, the choice of instance vs. static methods for value types becomes a wash because the compiler is effectively doing the same dispatch under the hood anyway

That’s true for value types whether or not we autogenerate, isn’t it?

(Also, for autogeneration, I like the idea of it being opt-out the same way default initializers are for a struct: if you provide a global operator implementation in the same module with the correct signature, the compiler doesn’t need to generate one.)

- for class types, regardless of whether one is a base of the other or both share a common third base type, neither static nor instance methods completely solve the problem and won't until/unless Swift supports multiple dispatch, and the proposed behavior is not a regression in those cases

I guess I’m not convinced of the chain of reasoning here. “Multi-method dispatch is the most correct way to solve the problem” is fine; “therefore, anything short of that isn’t worth doing” is where I get stuck. Instance methods partially solve the problem, and it’s possible (again, no data on hand) that they solve the problem in the majority of cases.

(It’s also possible that the prevalence of OO has made people prefer operators that can be dispatched based on the left-hand side, so I guess I’d want to go look at, e.g. Haskell and Perl to see what operators don’t fit in that bucket.)

I guess I’d summarize my stance as “this proposal enshrines our current problems with operator semantics in order to improve consistency in the syntax” (with “enshrines” meaning “makes harder to change later”), and that doesn’t seem like a good enough reason to change from what we have now.

(In particular, we don’t have any operator members now, besides in protocols. That means adding them in any form can probably be an additive change, or at least nearly so. Once we do, however, we’d have a hard time switching to a different model without breaking everyone’s code, and we should really stop breaking everyone’s code with new language changes at some point.)

Jordan

P.S. Thanks for taking my concerns seriously and taking the time to respond to them. :-)

···

On May 24, 2016, at 09:07, Tony Allevato <allevato@google.com> wrote:
On Mon, May 23, 2016 at 9:58 PM Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

Thanks for your feedback, Doug! I've addressed some of your concerns inline.

Proposal link:

  https://github.com/apple/swift-evolution/blob/master/proposals/0091-improving-operators-in-protocols.md

Hi all,

The review of "SE-0091: Improving operator requirements in protocols" begins now and runs through May 23. The proposal is available here:

My apologies for being very, very late with this review. As has been noted elsewhere, the core team has been rather DoS’d for the last few weeks, and even very important things are getting lost in the shuffle.

I support the goals of this proposal, but I’m strongly against the approach it takes because it is fundamentally based on forwarding functions in the global scope.

My Complaints with the Proposal
1) Adding an operator function to a type doesn’t “just work”, which is surprising. Let’s do something silly and add a ‘*’ operator to repeat strings:

extension String {
  static func *(lhs: String, rhs: Int) -> String { … }
}

If I try to use this in the obvious way

print(“hello” * 3)

I will get a compiler error. I have two paths at this point, neither of which is obvious: either I need to find (or author!) a protocol to conform to that allows my particular brand of ‘*’ to match and has a global forwarding operator, or I need to implement the operator at the global scope:

  func *(lhs: String, rhs: Int) -> String { ... }

The unambiguous answer here is that you need to define the answer at the global scope; no protocols come into play here. This proposal only discusses what happens *if* a protocol defines an operator as a *requirement*; that does not apply to the case of extending an existing type with a new operator, so it's unaffected by these proposed changes.

I'll concede that it has the possibility to introduce confusion as there are now two places where operators can be defined, with different meanings.

My point (1) is about that confusion, and also that the most natural place to put an operator—inside a type or extension thereof—is not the correct place to put the operator.

2) Creating a new operator now requires more boilerplate:
  a) An operator definition, e.g.,

infix operator +++ { }

  b) A protocol that describes this new operator,

protocol Concatable {
  func operator+++(lhs: Self, rhs: Self) -> Self
}

  c) A forwarding operator function based on that protocol

func operator+++<T : Concatable>(lhs: T, rhs: T) -> T {
  return T.+++(lhs, rhs)
}

Yes, creating a new operator shouldn’t be the easiest thing in the world, but that is a ton of boilerplate. Moreover…

This isn't the case—creating a new operator does not require defining a protocol. Defining a new infix operator like `+++` would work just as it did before; this proposal does not change that. I'm *not* proposing that all operators *must* be implemented through protocol conformance; I'm merely proposing changes to the way that they are implemented for conformances. If a user defines `+++`, they can implement it with a global function, without any protocol or trampoline introduced.

You’re correct that I am not required to introduce a protocol for the new operator. If I don’t, however, then we don’t get any of the type-checker performance or QoI benefits from this proposal. Worse, if we introduce the protocol later—and don’t go refactor all of the global operator functions into members—we also don’t get the type-checker performance or QoI benefits. So, even if the proposal doesn’t specifically require what I’m complaining about in (2), it’s very likely to happy in the standard library and become “lore” for how to define operators.

The additional burden is only on protocol authors (not authors of operators in general) to provide the trampoline method. My initial way of addressing that was to auto-generate the trampolines, eliminating the extra boilerplate, but that was unfortunately deemed too ambitious for Swift 3. However, I feel this still a step in the right direction and those issues can be resolved later without breaking anything proposed here, while still providing the other benefits described in the proposal.

Autogenerating trampolines eliminates one bit of the boilerplate, yes.

3) The protocols used to describe these operators aren’t really natural: they are bare-bones, purely-syntactic protocols that have no meaning other than to do forwarding. Just putting “+” into Arithmetic isn’t good enough: we’ll need another one for Strideable, and we’ll almost surely end up with a “HasBinaryPlus” protocol like this:

protocol HasBinaryPlus {
  func operator+(lhs: Self, rhs: Self) -> Self
}

so that other non-arithmetic types that want to introduce a binary plus with this form can opt to the protocol rather than having to write the forwarding function I complained about in (1). Moreover, Arithmetic will inherit this HasBinaryPlus. Scale that out and you have Arithmetic being composed of a pile of meaningless syntactic protocols: HasBinaryPlus, HasBinaryMinus, HasBinaryStar, HasBinarySlash, HasPrefixPlus, HasPrefixMinus. It makes Arithmetic confusing because the requirements are scattered.

It’s not even that there is just one protocol per operator, either: even just with the standard library, + will have at least two protocols associated with it: HasBinaryPlus and Strideable to cover the various cases in the standard library. It’s probably not enough, and there will surely be more protocols created for binary + simply to provide the forwarding functions.

Again, this isn't the case. Operators are not required to be implemented through protocols, but any protocol that requires an operator can include it and there is no reason that they would have to be restricted to one operator per protocol. Maybe this wasn't clear since I focused a lot on `Equatable` in my proposal for simplicity, but the motivating example was `FloatingPoint`, which would implement several:

    protocol FloatingPoint {
      static func +(lhs: Self, rhs: Self) -> Self
      static func -(lhs: Self, rhs: Self) -> Self
      static func *(lhs: Self, rhs: Self) -> Self
      static func /(lhs: Self, rhs: Self) -> Self
      // others...
}

As you can see, the protocol still has *semantic* meaning and is not just a bag of syntax.

If the trampoline functions don’t cover enough types, or we end up with lots of different trampoline functions (one for FloatingPoint, one for IntegerProtocol, another for Stridable, and so on), then we won’t actually end up with a significantly smaller overload set. So either we lose those benefits, or we end up slicing the protocols into smaller, reusable (but meaningless parts).

Let’s look at it this way: FloatingPoint will cover maybe 2-5 types (Float, Double, CGFloat (Darwin), Float80 (Intel), HalfFloat (maybe someday?)) . We’ll either need to abstract it further—into Arithmetic so we pick up the integral types, for example, or further so we can pick up String + String—or start adding more protocols that cover the same + syntax and have their own forwarding functions.

The Good Parts

With all that negative, one might get the impression that I don’t like operators in types. I think there are improvements here:

I) Writing an operator function in a type/extension of a type is far more natural that writing one at global scope for the common case. Even if you’re not planning on conforming to a protocol, it just feels right that (say) String + String should be defined in an extension of String. It’s better for tooling (which can more easily associate the operator + with the String type), code organization, works with the new meaning of the “private” access modifier, and simply feels like Swift.

II) The requirement to use “static” on the operator function requirement in the protocol makes perfect sense to me. It’s a lot clearer, and communicates the semantics better. I can’t recall why we didn’t do this in the first place.

III) The goal to reduce the total number of overloads is laudable. It can help type checker performance (fewer overloads == less exponential behavior) and improve diagnostics (fewer candidates to display on error). The key insight here is that we don’t want to consider both a generic operator based on some protocol (e.g., + for Arithmetic types) and the operator functions that are used to satisfy the corresponding requirement.

An Alternative Approach

Let’s accept (I) and (II). But, let’s make operator lookup always be global, so that it sees all operators defined at either module scope or within a type/extension of a type. This gives us the syntactic improvements of the SE-0091 “immediately”, and eliminates all five of my complaints above: the natural Swift thing of defining your functionality within the type or an extension thereof “just works”. It’s weird in the sense that operators will be the only place where we do such global lookup—finding entries at both global and type scope. However, SE-0091 is introducing a different weird name lookup rule, and it feels like there’s really no way to avoid it: we simply don’t want normal lexical name lookup for operators when they can be defined in types.

This approach does not (directly) give any of the type checker performance/QoI improvements of (III). However, we can achieve that by making the key insight of (III) part of the semantic model: when we find all operators, we also find the operators in the protocols themselves. The operators in the protocols are naturally generic, e.g., the Arithmetic + effectively has a generic function type like this:

  <Self: Arithmetic> (Self, Self) -> Self

which is basically what the forwarding functions look like in SE-0091 at a type level. Then, we say that we do not consider an operator function if it implements a protocol requirement, because the requirement is a generalization of all of the operator functions that satisfy that requirement. With this rule, we’re effectively getting the same effects of SE-0091’s approach to (III)—but it’s more automatic.

I like this suggestion very much, and I would support it—especially if it's easier to implement than the trampoline generation that I proposed for the same effect.

Thinking about it further, type checking that should be fairly straightforward, right? If we ignore classes/subclassing for the time being, an operator function can only have one or two arguments. Let's say we have the following:

    let t1: T
    let t2: T
    let t = t1 + t2

If we eliminate global lookup for operators, this means that the + operator *must* be implemented on T; so rather than searching the entire global namespace for +(lhs: T, rhs: T), it just has to look in T for a matching +.

Likewise, heterogeneous argument lists can still be supported:

    let t: T
    let u: U
    let v = t + u

The operator must live as a static function in T or U, or it doesn't exist. So lookup should again be fast. By looking up the operator in both T and U, as opposed to just one or the other, this supports users being able to define operators where they most logically make sense; for example, hypothetically:

    protocol CustomStringProtcol {
        static func +(lhs: Self, rhs: Character) -> Self
        static func +(lhs: Character, rhs: Self) -> Self
    }
    struct CustomString: CustomStringProtocol { ... }

    let t: Character
    let u: String
    let v = t + u // looks up + with compatible arguments in String and Character, finds it in String

We’ve thought about this, and it doesn’t work in Swift’s type system. It’s similar to the rule that C++ uses for operator lookup—called Argument Dependent Lookup, or ADL—which looks in the types of the operands (and a whole pile of types and namespaces related to it). But that doesn’t work in a language with bidirectional type inference like Swift has, because you have to also consider the return type of the operator and there’s not necessarily any point at which you know all of the input types and the result type. Let’s do something fun:

extension String {
  func /(lhs: Character, rhs: Character) -> String
}

let s: String = ‘a’ / ‘b’

We need to look in String to find this operator, which we can only do when we consider the return type. One doesn’t even need to involve the result type, because the way Swift handles literals makes it very hard to answer the question “what type is this expression going to have?”. Consider:

extension UInt {
  func +++(lhs: Int, rhs: UInt) -> UInt
}

let x = 1 +++ 2

There’s essentially no way to find that +++ based on the types, because literals don’t have types by themselves. However, if we did find the +++ based on some global lookup, the result will type-check.

There's the potential for ambiguity if both types implement operators that match, but that may not be cause for concern.

I think there’s always going to be a potential for ambiguity in this space. I’d only be particularly concerned about it if our rule caused people to have to duplicate operator functions in different types so that at least one of them would be found.

  - Doug

···

On Jun 10, 2016, at 8:24 AM, Tony Allevato <allevato@google.com> wrote:
On Thu, Jun 9, 2016 at 10:16 PM Douglas Gregor via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On May 17, 2016, at 8:33 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

+1; I'm on board. Thanks for doing the hard design work, Doug!

···

on Fri Jun 10 2016, Tony Allevato <swift-evolution@swift.org> wrote:

5) Forwarding functions aren’t good for tools. Under this proposal, if I
write “1 + 2” and use a tool to look at which “+” it resolved to, what will
we see? The generic forwarding operator. Even though I could look in the
source and see this:

extension Int {
  static func +(lhs: Int, rhs: Int) -> Int { … }
}

and even those that’s what will get called, my tools aren’t going to
interpret the body of the global forwarding function for + to resolve it in
the obvious way.

*The Good Parts*

With all that negative, one might get the impression that I don’t like
operators in types. I think there are improvements here:

I) Writing an operator function in a type/extension of a type is far more
natural that writing one at global scope for the common case. Even if
you’re not planning on conforming to a protocol, it just feels right that
(say) String + String should be defined in an extension of String. It’s
better for tooling (which can more easily associate the operator + with the
String type), code organization, works with the new meaning of the
“private” access modifier, and simply feels like Swift.

II) The requirement to use “static” on the operator function requirement
in the protocol makes perfect sense to me. It’s a lot clearer, and
communicates the semantics better. I can’t recall why we didn’t do this in
the first place.

III) The goal to reduce the total number of overloads is laudable. It can
help type checker performance (fewer overloads == less exponential
behavior) and improve diagnostics (fewer candidates to display on error).
The key insight here is that we don’t want to consider both a generic
operator based on some protocol (e.g., + for Arithmetic types) *and* the
operator functions that are used to satisfy the corresponding requirement.

*An Alternative Approach*

Let’s accept (I) and (II). But, let’s make operator lookup always be
global, so that it sees all operators defined at either module scope or
within a type/extension of a type. This gives us the syntactic improvements
of the SE-0091 “immediately”, and eliminates all five of my complaints
above: the natural Swift thing of defining your functionality within the
type or an extension thereof “just works”. It’s weird in the sense that
operators will be the only place where we do such global lookup—finding
entries at both global and type scope. However, SE-0091 is introducing a
different weird name lookup rule, and it feels like there’s really no way
to avoid it: we simply don’t want normal lexical name lookup for operators
when they can be defined in types.

This approach does not (directly) give any of the type checker
performance/QoI improvements of (III). However, we can achieve that by
making the key insight of (III) part of the semantic model: when we find
all operators, we also find the operators in the protocols themselves. The
operators in the protocols are naturally generic, e.g., the Arithmetic +
effectively has a generic function type like this:

<Self: Arithmetic> (Self, Self) -> Self

which is basically what the forwarding functions look like in SE-0091 at a
type level. Then, we say that we do not consider an operator function if it
implements a protocol requirement, because the requirement is a
generalization of all of the operator functions that satisfy that
requirement. With this rule, we’re effectively getting the same effects of
SE-0091’s approach to (III)—but it’s more automatic.

I like this suggestion very much, and I would support it—especially if it's
easier to implement than the trampoline generation that I proposed for the
same effect.

--
Dave

I think we don't need to invoke special compiler behavior. Namely, since
the trampoline is manually generated, do this:

Static:

static func ++(anyLabelYouWant value: inout Self) -> Self

Later:

postfix func ++ <T: SomeProtocol>(value: inout T) -> T {
  return T.++(anyLabelYouWant: &value)
}
···

On Tue, May 17, 2016 at 10:57 PM, Kevin Lundberg via swift-evolution < swift-evolution@swift.org> wrote:

I do like this to a point, but it ascribes special compiler behavior to
specific argument labels which feels odd. If someone defined another
operator function with a completely different argument label, would it just
be ignored? If a developer makes a typo, that could be a subtle bug that
may be confusing. Or should the compiler only allow those two argument
labels for unary operator functions? That feels strange since it's not a
restriction present anywhere else, and it starts to feel like the labels
have a dual purpose that also falls into the realm of keywords.

Either option doesn't quite sit right with me, even though the lack of
duplication that this solution has does look better, admittedly.

--
Kevin Lundberg

On May 18, 2016, at 12:57 AM, Brent Royal-Gordon <brent@architechies.com> > wrote:

>> Additionally, I am generally +1 for the same reasons as Brent, but I
>> have another caveat as well:
>>
>> Defining prefix and postfix functions looks like this in the proposal:
>>
>> static prefix func ++(value: inout Self) -> Self
>> static postfix func ++(value: inout Self) -> Self
>>
>> yet the proposal suggests calling them this way from these boilerplate
>> methods:
>>
>> prefix func ++ <T: SomeProtocol>(value: inout T) -> T {
>> return T.++(prefix: &value)
>> }
>> postfix func ++ <T: SomeProtocol>(value: inout T) -> T {
>> return T.++(postfix: &value)
>> }
>
> I actually found this bizarre too, but forgot to mention it. My
suggested solution runs in the other direction: We should require that
*all* unary operator declarations and references use `prefix` or `postfix`
as a parameter label. Thus, the trampoline operators themselves would be
written as:
>
> func ++ <T: SomeProtocol>(prefix value: inout T) -> T {
> return T.++(prefix: &value)
> }
>
> func ++ <T: SomeProtocol>(postfix value: inout T) -> T {
> return T.++(postfix: &value)
> }
>
> Not would be written as:
>
> func ! <B: BooleanType>(prefix value: B) -> Bool
>
> While force-unwrap (if we had inout return values) would be written:
>
> func ! <T>(postfix value: inout Optional<T>) -> inout T
>
> `prefix` and `postfix` would be eliminated from the language as
declaration modifiers, except when declaring custom operators (which is
already the Land Of Ad-Hoc Syntax).
>
> --
> Brent Royal-Gordon
> Architechies
>

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

Or should the compiler only allow those two argument labels for unary operator functions? That feels strange since it's not a restriction present anywhere else, and it starts to feel like the labels have a dual purpose that also falls into the realm of keywords.

A similar restriction exists on operators currently. Today, Swift does not allow keywords on operator parameters:

23> prefix func !<T>(prefix value: T) {}
repl.swift:23:18: error: operator cannot have keyword arguments
prefix func !<T>(prefix value: T) {}
                 ^~~~~~~

What I'm suggesting is that, on unary operators, Swift require either the keyword "prefix" or "postfix" on the argument, and use that to differentiate between prefix and postfix operators. No other keyword would be used or permitted, and no keyword would be used or permitted on infix operators' arguments.

···

--
Brent Royal-Gordon
Architechies

> Or should the compiler only allow those two argument labels for unary
operator functions? That feels strange since it's not a restriction present
anywhere else, and it starts to feel like the labels have a dual purpose
that also falls into the realm of keywords.

A similar restriction exists on operators currently. Today, Swift does not
allow keywords on operator parameters:

23> prefix func !<T>(prefix value: T) {}
repl.swift:23:18: error: operator cannot have keyword arguments
prefix func !<T>(prefix value: T) {}
                 ^~~~~~~

What I'm suggesting is that, on unary operators, Swift require either the
keyword "prefix" or "postfix" on the argument, and use that to
differentiate between prefix and postfix operators. No other keyword would
be used or permitted, and no keyword would be used or permitted on infix
operators' arguments.

I would be supportive of this. The syntax for static prefix/postfix
operators in that proposal was a matter of some earlier debate (in e-mail
threads, and a couple comments in the pull request <
https://github.com/apple/swift-evolution/pull/283&gt;\) and was one of the
parts of the proposal I thought felt less natural.

I think we should definitely have a solution that avoids duplication (so
not having a `prefix` as a leading keyword and as an argument label). Since
prefix/postfix operators are required to only have one argument, and those
arguments must already not have labels, introducing prefix/postfix as
labels instead of keywords to distinguish them seems natural.

The other major benefit (which I mention in a PR comment above) is that, if
you have a prefix and postfix operator with the same name, there is no way
currently to get function references to both of them (for passing to other
algorithms), because there are no labels used to distinguish them. For
example, if you use `++` as a function reference (ignoring the fact that
it's deprecated), the REPL takes the last one that was defined. (I didn't
test what the compiler does, but presumably it's arbitrary depending on the
ordering of the data structures used to store those operators.) Using
argument labels would solve this problem as well.

···

On Wed, May 18, 2016 at 12:10 AM Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

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

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal says
"This document does not propose that the current way of defining operators
be removed or changed at this time. Rather, we describe an addition that
specifically provides improvements for protocol operator requirements."

Later, however, there is a "Deprecation of non-static protocol operators"
section which suggest to do exactly that, and this is reiterated in the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an oversight.

I could probably do a better job of clarifying the wording here. The proposal does *not* deprecate *all* global operator overloads. Global operators can still be implemented as they have been in Swift. So if you have a concrete type like `struct Matrix`, you can still define at the global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator requirements inside protocols (by making the functions static) and the manner by which subtypes conform (ditto, through static methods instead of global functions).

OK, I guess the unclear part is when you talk about "an addition that specifically provides improvements for protocol operator requirements." This is not just an addition; it's intended to completely replace the protocol operator syntax.

2) The method signatures in the examples are not up to date with the current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the syntax changes don't appear to apply to operator functions. Since they are a special case that don't have argument labels, it wouldn't make sense to require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods, and are called like methods. They happen to have funny names, but they are still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels, but making them behave differently from normal methods would be inconsistent, and a step backwards from all the progress that has been made in Swift 3 on that front.

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less clear than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for consistency. As you mention, there's a stronger argument to be made for assignment operators to have "left hand side is the receiver" semantics than there are for standard infix operators, but from a consistency point of view (and ease of learning), I think having everything static and the signatures of the static operators matching those of the global operators is preferable.

I think this would better be left as a choice to the author of the protocol. There doesn't seem to be any technical reason to place this restriction.

The problem with this is that people will make different choices. This may result in boilerplate where a type has to provide both static *and* instance methods to conform to different protocols. Better to avoid that possibility.

What benefit do you perceive in allowing operator instance methods?

···

On May 18, 2016, at 3:00 PM, Nicola Salmoria via swift-evolution <swift-evolution@swift.org> wrote:
On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com <mailto:allevato@google.com>> wrote:
On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

(Which is also why, as I mentioned in a previous reply, I would be open to dropping the prefix/postfix keyword and making it an argument label instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names is for
arithmetical operators. Custom operators like ... are better expressed as
methods with more significant names.

If there is a strong case where an operator is better implemented as a global operator and a named method, this proposal still allows that, since it's not deprecating all global operator definitions. A protocol could certainly have a requirement that is a named method, and provide a global generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative uses of
such names, which we would be ruling out for no particular reason. This is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose name is the same as a defined operator should conform to the requirements (such as argument count) of that operator. It's certainly worth discussion, though! With that being said, it may be easier on users to "rule something out" now and open it up later if need be, rather than to leave it open for people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with the same name and different signature, which Swift allows without problems. Again, I think this is a choice that the author of the protocol should make, and there doesn't seem to be any technical reason to require otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm not
going to talk much about it, I only want to mention that it would make sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos, etc.
It could make sense to give them the same treatment as operators, declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or even for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a change to
Swift?

Absolutely. The handling of operators in protocols has been one of the worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much less
advanced.

> * How much effort did you put into your review? A glance, a quick reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads on
the mailing list.

--
Nicola

_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal
says
"This document does not propose that the current way of defining
operators
be removed or changed at this time. Rather, we describe an addition that
specifically provides improvements for protocol operator requirements."

Later, however, there is a "Deprecation of non-static protocol operators"
section which suggest to do exactly that, and this is reiterated in the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an oversight.

I could probably do a better job of clarifying the wording here. The
proposal does *not* deprecate *all* global operator overloads. Global
operators can still be implemented as they have been in Swift. So if you
have a concrete type like `struct Matrix`, you can still define at the
global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator
requirements inside protocols (by making the functions static) and the
manner by which subtypes conform (ditto, through static methods instead of
global functions).

OK, I guess the unclear part is when you talk about "an addition that
specifically provides improvements for protocol operator requirements."
This is not just an addition; it's intended to completely replace the
protocol operator syntax.

2) The method signatures in the examples are not up to date with the
current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the
syntax changes don't appear to apply to operator functions. Since they are
a special case that don't have argument labels, it wouldn't make sense to
require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods, and
are called like methods. They happen to have funny names, but they are
still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels,
but making them behave differently from normal methods would be
inconsistent, and a step backwards from all the progress that has been made
in Swift 3 on that front.

What I'm saying is, if you look at the Swift 3 branch of stdlib, global
operator functions still do not have argument labels. Picking one at
random:

If you're arguing that those functions should be forced to include `_`
placeholders, that's fine, but it's not accurate to say that the way
they're written in this proposal is a step backwards from all the progress
made in Swift 3. It is *consistent* with the way global operator functions
are currently declared in Swift 3.

If it changes there, then it should change here as well. But they should be
the same, and making that change for global operator functions is not part
of the scope of this proposal.

···

On Wed, May 18, 2016 at 1:00 PM Nicola Salmoria <nicola.salmoria@gmail.com> wrote:

On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com> > wrote:

On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution < >> swift-evolution@swift.org> wrote:

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are
other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less clear
than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one
would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for
consistency. As you mention, there's a stronger argument to be made for
assignment operators to have "left hand side is the receiver" semantics
than there are for standard infix operators, but from a consistency point
of view (and ease of learning), I think having everything static and the
signatures of the static operators matching those of the global operators
is preferable.

I think this would better be left as a choice to the author of the
protocol. There doesn't seem to be any technical reason to place this
restriction.

(Which is also why, as I mentioned in a previous reply, I would be open
to dropping the prefix/postfix keyword and making it an argument label
instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators
with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names is
for
arithmetical operators. Custom operators like ... are better expressed as
methods with more significant names.

If there is a strong case where an operator is better implemented as a
global operator and a named method, this proposal still allows that, since
it's not deprecating all global operator definitions. A protocol could
certainly have a requirement that is a named method, and provide a global
generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say
there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative
uses of
such names, which we would be ruling out for no particular reason. This
is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose
name is the same as a defined operator should conform to the requirements
(such as argument count) of that operator. It's certainly worth discussion,
though! With that being said, it may be easier on users to "rule something
out" now and open it up later if need be, rather than to leave it open for
people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with the
same name and different signature, which Swift allows without problems.
Again, I think this is a choice that the author of the protocol should
make, and there doesn't seem to be any technical reason to require
otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm
not
going to talk much about it, I only want to mention that it would make
sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos,
etc.
It could make sense to give them the same treatment as operators,
declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or even
for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a
change to
Swift?

Absolutely. The handling of operators in protocols has been one of the
worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much less
advanced.

> * How much effort did you put into your review? A glance, a quick
reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads on
the mailing list.

--
Nicola

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

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal
says
"This document does not propose that the current way of defining
operators
be removed or changed at this time. Rather, we describe an addition that
specifically provides improvements for protocol operator requirements."

Later, however, there is a "Deprecation of non-static protocol operators"
section which suggest to do exactly that, and this is reiterated in the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an oversight.

I could probably do a better job of clarifying the wording here. The
proposal does *not* deprecate *all* global operator overloads. Global
operators can still be implemented as they have been in Swift. So if you
have a concrete type like `struct Matrix`, you can still define at the
global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator
requirements inside protocols (by making the functions static) and the
manner by which subtypes conform (ditto, through static methods instead of
global functions).

OK, I guess the unclear part is when you talk about "an addition that
specifically provides improvements for protocol operator requirements."
This is not just an addition; it's intended to completely replace the
protocol operator syntax.

2) The method signatures in the examples are not up to date with the
current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the
syntax changes don't appear to apply to operator functions. Since they are
a special case that don't have argument labels, it wouldn't make sense to
require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods, and
are called like methods. They happen to have funny names, but they are
still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels,
but making them behave differently from normal methods would be
inconsistent, and a step backwards from all the progress that has been made
in Swift 3 on that front.

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are
other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less clear
than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one
would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for
consistency. As you mention, there's a stronger argument to be made for
assignment operators to have "left hand side is the receiver" semantics
than there are for standard infix operators, but from a consistency point
of view (and ease of learning), I think having everything static and the
signatures of the static operators matching those of the global operators
is preferable.

I think this would better be left as a choice to the author of the
protocol. There doesn't seem to be any technical reason to place this
restriction.

The problem with this is that people will make different choices. This
may result in boilerplate where a type has to provide both static *and*
instance methods to conform to different protocols. Better to avoid that
possibility.

Do you have a specific example in mind?

I expect the stdlib to illustrate what idiomatic Swift is expected to be,
and people to follow suit for the standard operators.

What benefit do you perceive in allowing operator instance methods?

The fact that operators like += are more naturally implemented as an
instance method than as a static method is already a clear benefit to me.

But in general, there aren't only the standard operators; people will
define custom operators and will have different needs depending on their
semantics. If there isn't a technical limitation, I don't see a reason to
add arbitrary restrictions.

···

On Wed, May 18, 2016 at 10:03 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On May 18, 2016, at 3:00 PM, Nicola Salmoria via swift-evolution < > swift-evolution@swift.org> wrote:
On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com> > wrote:

On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution < >> swift-evolution@swift.org> wrote:

(Which is also why, as I mentioned in a previous reply, I would be open
to dropping the prefix/postfix keyword and making it an argument label
instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators
with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names is
for
arithmetical operators. Custom operators like ... are better expressed as
methods with more significant names.

If there is a strong case where an operator is better implemented as a
global operator and a named method, this proposal still allows that, since
it's not deprecating all global operator definitions. A protocol could
certainly have a requirement that is a named method, and provide a global
generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say
there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative
uses of
such names, which we would be ruling out for no particular reason. This
is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose
name is the same as a defined operator should conform to the requirements
(such as argument count) of that operator. It's certainly worth discussion,
though! With that being said, it may be easier on users to "rule something
out" now and open it up later if need be, rather than to leave it open for
people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with the
same name and different signature, which Swift allows without problems.
Again, I think this is a choice that the author of the protocol should
make, and there doesn't seem to be any technical reason to require
otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm
not
going to talk much about it, I only want to mention that it would make
sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos,
etc.
It could make sense to give them the same treatment as operators,
declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or even
for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a
change to
Swift?

Absolutely. The handling of operators in protocols has been one of the
worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much less
advanced.

> * How much effort did you put into your review? A glance, a quick
reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads on
the mailing list.

--
Nicola

_______________________________________________
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

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of
removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal
says
"This document does not propose that the current way of defining
operators
be removed or changed at this time. Rather, we describe an addition that
specifically provides improvements for protocol operator requirements."

Later, however, there is a "Deprecation of non-static protocol
operators"
section which suggest to do exactly that, and this is reiterated in the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an oversight.

I could probably do a better job of clarifying the wording here. The
proposal does *not* deprecate *all* global operator overloads. Global
operators can still be implemented as they have been in Swift. So if you
have a concrete type like `struct Matrix`, you can still define at the
global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator
requirements inside protocols (by making the functions static) and the
manner by which subtypes conform (ditto, through static methods instead of
global functions).

OK, I guess the unclear part is when you talk about "an addition that
specifically provides improvements for protocol operator requirements."
This is not just an addition; it's intended to completely replace the
protocol operator syntax.

2) The method signatures in the examples are not up to date with the
current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the
syntax changes don't appear to apply to operator functions. Since they are
a special case that don't have argument labels, it wouldn't make sense to
require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods, and
are called like methods. They happen to have funny names, but they are
still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels,
but making them behave differently from normal methods would be
inconsistent, and a step backwards from all the progress that has been made
in Swift 3 on that front.

What I'm saying is, if you look at the Swift 3 branch of stdlib, global
operator functions still do not have argument labels. Picking one at
random:
https://github.com/apple/swift/blob/swift-3.0-branch/stdlib/public/core/String.swift#L329

If you're arguing that those functions should be forced to include `_`
placeholders, that's fine, but it's not accurate to say that the way
they're written in this proposal is a step backwards from all the progress
made in Swift 3. It is *consistent* with the way global operator functions
are currently declared in Swift 3.

If it changes there, then it should change here as well. But they should
be the same, and making that change for global operator functions is not
part of the scope of this proposal.

I'm not talking about the global operator functions; I'm talking about the
methods inside the protocol, which are methods and are called like methods;
they are not operators.

···

On Wed, May 18, 2016 at 10:27 PM, Tony Allevato <allevato@google.com> wrote:

On Wed, May 18, 2016 at 1:00 PM Nicola Salmoria <nicola.salmoria@gmail.com> > wrote:

On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com> >> wrote:

On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution < >>> swift-evolution@swift.org> wrote:

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need
to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are
other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less clear
than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one
would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for
consistency. As you mention, there's a stronger argument to be made for
assignment operators to have "left hand side is the receiver" semantics
than there are for standard infix operators, but from a consistency point
of view (and ease of learning), I think having everything static and the
signatures of the static operators matching those of the global operators
is preferable.

I think this would better be left as a choice to the author of the
protocol. There doesn't seem to be any technical reason to place this
restriction.

(Which is also why, as I mentioned in a previous reply, I would be open
to dropping the prefix/postfix keyword and making it an argument label
instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the
dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators
with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names is
for
arithmetical operators. Custom operators like ... are better expressed
as
methods with more significant names.

If there is a strong case where an operator is better implemented as a
global operator and a named method, this proposal still allows that, since
it's not deprecating all global operator definitions. A protocol could
certainly have a requirement that is a named method, and provide a global
generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say
there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative
uses of
such names, which we would be ruling out for no particular reason. This
is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose
name is the same as a defined operator should conform to the requirements
(such as argument count) of that operator. It's certainly worth discussion,
though! With that being said, it may be easier on users to "rule something
out" now and open it up later if need be, rather than to leave it open for
people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with the
same name and different signature, which Swift allows without problems.
Again, I think this is a choice that the author of the protocol should
make, and there doesn't seem to be any technical reason to require
otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm
not
going to talk much about it, I only want to mention that it would make
sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos,
etc.
It could make sense to give them the same treatment as operators,
declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or even
for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a
change to
Swift?

Absolutely. The handling of operators in protocols has been one of the
worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar
feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much less
advanced.

> * How much effort did you put into your review? A glance, a quick
reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads
on
the mailing list.

--
Nicola

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

As Brent pointed out in his reply, without multiple dispatch, you don't really benefit from privileging the lhs argument, and in fact you can end up in situations where the behavior is surprising if you don't implement both orders. For example, in your (NSString, NSURL) example, each class would have to be extended to explicitly support comparison with the other in order to support commutativity if equality was an instance method. I'm not sure that's any better for those particular types than just having the operators global in the first place.

I assume you’d still have to implement it with NSObject as the “other” type, like you do in Objective-C. You’d just return false in that case.

It is rather unfortunate, though, that your `(Self, Self)` operator essentially becomes `(Self, NSObject)` once `NSObject` conforms. I mean, Swift will at least *tell* you this is happening and force you to write an `other as? Self` test, but the way the type of the right-hand side gets pinned down while the left-hand side stays unspecified is just strange.

(Incidentally, I think rather than returning `false` here, you probably ought to say:

  guard let other = other as? Self else {
    return super == other
  }

Would you be able to use `super` in that way? You probably ought to, if we're planning to use left-side dispatch.)

Meanwhile, a multi dispatch solution would send two otherwise unrelated types to `NSObject`'s implementation, which would `===` the two instances, discover they were not identical, and return `nil`. More specific implementations would not even have to think about this case; the method dispatch would just do it for them. I don't know if that solution is feasible, but if it is, it seems clearly correct—it simply gives you the right behavior every single time with no hand-wringing about type mismatches.

(On the other hand, if we *do* use left-side dispatch, we might be able to solve the `isEqual` interop problems complained about upthread: if `==` is implicitly given an `@objc(isEqual:)` attribute, and `isEqual` definitions or calls are fix-it-ed to `==`, the problem essentially solves itself.)

···

--
Brent Royal-Gordon
Architechies

>
> How hard would it be to e.g. simply allow something like this:
>
> func ==<T:Equatable>(lhs: T, rhs: T) -> Bool {
> return lhs T.== rhs
> }
>
> …instead of the `T.==(lhs,rhs)` syntax?

Yeah, I've been meaning to suggest that. And 'T.++(x)' and '(x)T.++' for prefix and postfix, respectfully.

The "fully-qualified operator" syntax was one that was discussed in some of the original e-mail threads. It was part an earlier draft of the proposal, but I changed it to the function call syntax because

- visually, it's a bit tricky to parse with the eyes

I agree, but IMHO I would anticipate that each time I needed to use a pre/post-fix method, I’d have to go to the reference and double-check whether:

  T.++(prefix: foo)

…means “`foo` is prefixed-to `++`” or “the prefix `++` is prefixed-before `foo`”, and vice-versa for `postfix`; the point of confusion is that *usually* the argument label describe the argument, not the “function position” as it were.

Not likely to be a huge problem but likely to be confusing on the rare occasions it’s actually necessary to pick the right one.

Something like `prefixedTo:` and `postfixedTo:` would be more self-explanatory but seem clunky; perhaps a better formulation is possible here?

- I wasn't sure whether it would pose similar problems for the compiler to parse
- It doesn't solve the (admittedly edge case) issue of not being able to refer distinctly to prefix/postfix operators that have the same name as a first-class function without wrapping the invocation in a closure

It’s definitely an edge case but in some ways it just illustrates the tradeoff being made under this proposal. For this *specific* use case the following are IMHO increasingly self-explanatory:

- `foo.map(T.++)`
- `foo.map(T.++(prefix:))`
- `foo.map(T.incrementThenReturn(_:))`

…with the last approach being clearer in this one scenario, but still having all the other drawbacks that motivated to this proposal.

I can’t think of a natural scenario where this might come up; maybe something with `!` but even there I can’t think of one.

- Chris Lattner recommended changing it to the function call syntax after a core team discussion before the proposal PR was accepted, so it's possible they have more insight into the parsing/implementation issues?

That seems likely and reasonable.

After reflecting on it the main gripe I’d have is I don’t think the `prefix:/postfix:` labels do an ideal job of their roles in this proposal, but there’s probably a better approach to be found within the confines of “normal function” syntax.

···

On May 24, 2016, at 11:13 AM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:
On Mon, May 23, 2016 at 9:20 PM David Sweeris via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> On May 18, 2016, at 18:07, plx via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

- Dave Sweeris
_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution

I like that this applies the “postfix” or “prefix” labels to what they qualify: the operator.
The original proposal applies them to the parameter, which makes my head spin.

Cheers,
Guillaume Lessard

···

On 18 mai 2016, at 15:09, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

Yeah, also good points. Can I propose maybe another approach?

T.operator(+, a, b)
T.operator(prefix: ++, a)
T.operator(postfix: ++, a)

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal says
"This document does not propose that the current way of defining operators
be removed or changed at this time. Rather, we describe an addition that
specifically provides improvements for protocol operator requirements."

Later, however, there is a "Deprecation of non-static protocol operators"
section which suggest to do exactly that, and this is reiterated in the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an oversight.

I could probably do a better job of clarifying the wording here. The proposal does *not* deprecate *all* global operator overloads. Global operators can still be implemented as they have been in Swift. So if you have a concrete type like `struct Matrix`, you can still define at the global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator requirements inside protocols (by making the functions static) and the manner by which subtypes conform (ditto, through static methods instead of global functions).

OK, I guess the unclear part is when you talk about "an addition that specifically provides improvements for protocol operator requirements." This is not just an addition; it's intended to completely replace the protocol operator syntax.

2) The method signatures in the examples are not up to date with the current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the syntax changes don't appear to apply to operator functions. Since they are a special case that don't have argument labels, it wouldn't make sense to require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods, and are called like methods. They happen to have funny names, but they are still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels, but making them behave differently from normal methods would be inconsistent, and a step backwards from all the progress that has been made in Swift 3 on that front.

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less clear than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for consistency. As you mention, there's a stronger argument to be made for assignment operators to have "left hand side is the receiver" semantics than there are for standard infix operators, but from a consistency point of view (and ease of learning), I think having everything static and the signatures of the static operators matching those of the global operators is preferable.

I think this would better be left as a choice to the author of the protocol. There doesn't seem to be any technical reason to place this restriction.

The problem with this is that people will make different choices. This may result in boilerplate where a type has to provide both static *and* instance methods to conform to different protocols. Better to avoid that possibility.

Do you have a specific example in mind?

No, it’s not possible today so there is no existing code to reference. However, if you give people a choice some people will inevitably make a different choice than other people.

I expect the stdlib to illustrate what idiomatic Swift is expected to be, and people to follow suit for the standard operators.

That is exactly what this proposal is attempting to do, and in a way that the language enforces the decision.

What benefit do you perceive in allowing operator instance methods?

The fact that operators like += are more naturally implemented as an instance method than as a static method is already a clear benefit to me.

Are you specifically thinking of assignment operators? If so, I wouldn’t have a problem if the proposal specified assignment operators as instance methods and all other operators as static methods. As long as there is only one way to define a specific operator requirement I will be happy.

But in general, there aren't only the standard operators; people will define custom operators and will have different needs depending on their semantics. If there isn't a technical limitation, I don't see a reason to add arbitrary restrictions.

There is no difference in capability here so I’m not sure what you mean by having different “needs”.

···

On May 18, 2016, at 3:14 PM, Nicola Salmoria <nicola.salmoria@gmail.com> wrote:
On Wed, May 18, 2016 at 10:03 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On May 18, 2016, at 3:00 PM, Nicola Salmoria via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com <mailto:allevato@google.com>> wrote:
On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

(Which is also why, as I mentioned in a previous reply, I would be open to dropping the prefix/postfix keyword and making it an argument label instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names is for
arithmetical operators. Custom operators like ... are better expressed as
methods with more significant names.

If there is a strong case where an operator is better implemented as a global operator and a named method, this proposal still allows that, since it's not deprecating all global operator definitions. A protocol could certainly have a requirement that is a named method, and provide a global generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative uses of
such names, which we would be ruling out for no particular reason. This is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose name is the same as a defined operator should conform to the requirements (such as argument count) of that operator. It's certainly worth discussion, though! With that being said, it may be easier on users to "rule something out" now and open it up later if need be, rather than to leave it open for people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with the same name and different signature, which Swift allows without problems. Again, I think this is a choice that the author of the protocol should make, and there doesn't seem to be any technical reason to require otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm not
going to talk much about it, I only want to mention that it would make sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos, etc.
It could make sense to give them the same treatment as operators, declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or even for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a change to
Swift?

Absolutely. The handling of operators in protocols has been one of the worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much less
advanced.

> * How much effort did you put into your review? A glance, a quick reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads on
the mailing list.

--
Nicola

_______________________________________________
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

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of
removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal
says
"This document does not propose that the current way of defining
operators
be removed or changed at this time. Rather, we describe an addition
that
specifically provides improvements for protocol operator requirements."

Later, however, there is a "Deprecation of non-static protocol
operators"
section which suggest to do exactly that, and this is reiterated in the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an
oversight.

I could probably do a better job of clarifying the wording here. The
proposal does *not* deprecate *all* global operator overloads. Global
operators can still be implemented as they have been in Swift. So if you
have a concrete type like `struct Matrix`, you can still define at the
global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator
requirements inside protocols (by making the functions static) and the
manner by which subtypes conform (ditto, through static methods instead of
global functions).

OK, I guess the unclear part is when you talk about "an addition that
specifically provides improvements for protocol operator requirements."
This is not just an addition; it's intended to completely replace the
protocol operator syntax.

2) The method signatures in the examples are not up to date with the
current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the
syntax changes don't appear to apply to operator functions. Since they are
a special case that don't have argument labels, it wouldn't make sense to
require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods,
and are called like methods. They happen to have funny names, but they are
still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels,
but making them behave differently from normal methods would be
inconsistent, and a step backwards from all the progress that has been made
in Swift 3 on that front.

What I'm saying is, if you look at the Swift 3 branch of stdlib, global
operator functions still do not have argument labels. Picking one at
random:
https://github.com/apple/swift/blob/swift-3.0-branch/stdlib/public/core/String.swift#L329

If you're arguing that those functions should be forced to include `_`
placeholders, that's fine, but it's not accurate to say that the way
they're written in this proposal is a step backwards from all the progress
made in Swift 3. It is *consistent* with the way global operator functions
are currently declared in Swift 3.

If it changes there, then it should change here as well. But they should
be the same, and making that change for global operator functions is not
part of the scope of this proposal.

I'm not talking about the global operator functions; I'm talking about the
methods inside the protocol, which are methods and are called like methods;
they are not operators.

Thanks for expressing this so clearly. I'm of the same feeling but fumbled
the communication of it.

On re-evaluation, I wonder if this proposal as it is would be a
sufficiently large improvement. It's essentially permitting the use of
characters reserved for operators in static method names, but it adds a set
of somewhat inconsistent rules for how those functions are to be declared
and called. As mentioned earlier, `T....(x, y)` looks rather unfortunate,
and since automatic trampolines are out of scope, I wonder if what we have
currently (naming static methods using words) is altogether that bad. Maybe
we could just standardize those names and be done with it; on a cursory
look, that seems to be Rust's approach.

···

On Wed, May 18, 2016 at 3:37 PM, Nicola Salmoria via swift-evolution < swift-evolution@swift.org> wrote:

On Wed, May 18, 2016 at 10:27 PM, Tony Allevato <allevato@google.com> > wrote:

On Wed, May 18, 2016 at 1:00 PM Nicola Salmoria < >> nicola.salmoria@gmail.com> wrote:

On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com> >>> wrote:

On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution < >>>> swift-evolution@swift.org> wrote:

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need
to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are
other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less
clear than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one
would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for
consistency. As you mention, there's a stronger argument to be made for
assignment operators to have "left hand side is the receiver" semantics
than there are for standard infix operators, but from a consistency point
of view (and ease of learning), I think having everything static and the
signatures of the static operators matching those of the global operators
is preferable.

I think this would better be left as a choice to the author of the
protocol. There doesn't seem to be any technical reason to place this
restriction.

(Which is also why, as I mentioned in a previous reply, I would be open
to dropping the prefix/postfix keyword and making it an argument label
instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the
dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators
with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names
is for
arithmetical operators. Custom operators like ... are better expressed
as
methods with more significant names.

If there is a strong case where an operator is better implemented as a
global operator and a named method, this proposal still allows that, since
it's not deprecating all global operator definitions. A protocol could
certainly have a requirement that is a named method, and provide a global
generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say
there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative
uses of
such names, which we would be ruling out for no particular reason.
This is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose
name is the same as a defined operator should conform to the requirements
(such as argument count) of that operator. It's certainly worth discussion,
though! With that being said, it may be easier on users to "rule something
out" now and open it up later if need be, rather than to leave it open for
people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with
the same name and different signature, which Swift allows without problems.
Again, I think this is a choice that the author of the protocol should
make, and there doesn't seem to be any technical reason to require
otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm
not
going to talk much about it, I only want to mention that it would make
sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos,
etc.
It could make sense to give them the same treatment as operators,
declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or
even for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a
change to
Swift?

Absolutely. The handling of operators in protocols has been one of the
worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar
feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much
less
advanced.

> * How much effort did you put into your review? A glance, a quick
reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads
on
the mailing list.

--
Nicola

_______________________________________________
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

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal says
"This document does not propose that the current way of defining operators
be removed or changed at this time. Rather, we describe an addition that
specifically provides improvements for protocol operator requirements."

Later, however, there is a "Deprecation of non-static protocol operators"
section which suggest to do exactly that, and this is reiterated in the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an oversight.

I could probably do a better job of clarifying the wording here. The proposal does *not* deprecate *all* global operator overloads. Global operators can still be implemented as they have been in Swift. So if you have a concrete type like `struct Matrix`, you can still define at the global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator requirements inside protocols (by making the functions static) and the manner by which subtypes conform (ditto, through static methods instead of global functions).

OK, I guess the unclear part is when you talk about "an addition that specifically provides improvements for protocol operator requirements." This is not just an addition; it's intended to completely replace the protocol operator syntax.

2) The method signatures in the examples are not up to date with the current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the syntax changes don't appear to apply to operator functions. Since they are a special case that don't have argument labels, it wouldn't make sense to require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods, and are called like methods. They happen to have funny names, but they are still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels, but making them behave differently from normal methods would be inconsistent, and a step backwards from all the progress that has been made in Swift 3 on that front.

What I'm saying is, if you look at the Swift 3 branch of stdlib, global operator functions still do not have argument labels. Picking one at random: https://github.com/apple/swift/blob/swift-3.0-branch/stdlib/public/core/String.swift#L329

If you're arguing that those functions should be forced to include `_` placeholders, that's fine, but it's not accurate to say that the way they're written in this proposal is a step backwards from all the progress made in Swift 3. It is *consistent* with the way global operator functions are currently declared in Swift 3.

If it changes there, then it should change here as well. But they should be the same, and making that change for global operator functions is not part of the scope of this proposal.

I'm not talking about the global operator functions; I'm talking about the methods inside the protocol, which are methods and are called like methods; they are not operators.

Thanks for expressing this so clearly. I'm of the same feeling but fumbled the communication of it.

On re-evaluation, I wonder if this proposal as it is would be a sufficiently large improvement. It's essentially permitting the use of characters reserved for operators in static method names, but it adds a set of somewhat inconsistent rules for how those functions are to be declared and called. As mentioned earlier, `T....(x, y)` looks rather unfortunate, and since automatic trampolines are out of scope, I wonder if what we have currently (naming static methods using words) is altogether that bad. Maybe we could just standardize those names and be done with it; on a cursory look, that seems to be Rust's approach.

Personally, I find Rust’s approach a bit ugly. And Tony makes a very good point in the proposal that using words requires us to learn the word associated with each operator.

I noted some concerns about this proposal not including automatic trampolines. However, if we are not going to be able to make a breaking change like this in the standard library after Swift 3 I do think it is important to give this proposal significant consideration even without them. Automatic trampolines can be added later but we may not have the opportunity to fix the standard library protocols later.

···

On May 18, 2016, at 3:48 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
On Wed, May 18, 2016 at 3:37 PM, Nicola Salmoria via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
On Wed, May 18, 2016 at 10:27 PM, Tony Allevato <allevato@google.com <mailto:allevato@google.com>> wrote:
On Wed, May 18, 2016 at 1:00 PM Nicola Salmoria <nicola.salmoria@gmail.com <mailto:nicola.salmoria@gmail.com>> wrote:
On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com <mailto:allevato@google.com>> wrote:
On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less clear than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for consistency. As you mention, there's a stronger argument to be made for assignment operators to have "left hand side is the receiver" semantics than there are for standard infix operators, but from a consistency point of view (and ease of learning), I think having everything static and the signatures of the static operators matching those of the global operators is preferable.

I think this would better be left as a choice to the author of the protocol. There doesn't seem to be any technical reason to place this restriction.

(Which is also why, as I mentioned in a previous reply, I would be open to dropping the prefix/postfix keyword and making it an argument label instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names is for
arithmetical operators. Custom operators like ... are better expressed as
methods with more significant names.

If there is a strong case where an operator is better implemented as a global operator and a named method, this proposal still allows that, since it's not deprecating all global operator definitions. A protocol could certainly have a requirement that is a named method, and provide a global generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative uses of
such names, which we would be ruling out for no particular reason. This is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose name is the same as a defined operator should conform to the requirements (such as argument count) of that operator. It's certainly worth discussion, though! With that being said, it may be easier on users to "rule something out" now and open it up later if need be, rather than to leave it open for people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with the same name and different signature, which Swift allows without problems. Again, I think this is a choice that the author of the protocol should make, and there doesn't seem to be any technical reason to require otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm not
going to talk much about it, I only want to mention that it would make sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos, etc.
It could make sense to give them the same treatment as operators, declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or even for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a change to
Swift?

Absolutely. The handling of operators in protocols has been one of the worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much less
advanced.

> * How much effort did you put into your review? A glance, a quick reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads on
the mailing list.

--
Nicola

_______________________________________________
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

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

> * What is your evaluation of the proposal?

I'm generally in strong support, having long been a proponent of
removing
operators from protocols (the first occurrence was in this thread:
http://article.gmane.org/gmane.comp.lang.swift.evolution/7935\)

I have several comments about the details of the proposal, however.

1) At the beginning, in the "Proposed solution" section, the proposal
says
"This document does not propose that the current way of defining
operators
be removed or changed at this time. Rather, we describe an addition
that
specifically provides improvements for protocol operator
requirements."

Later, however, there is a "Deprecation of non-static protocol
operators"
section which suggest to do exactly that, and this is reiterated in
the
"Impact on existing code" section.

Since I think that the deprecation of global operator overloads is the
crucial point of the proposal, I assume that the former is an
oversight.

I could probably do a better job of clarifying the wording here. The
proposal does *not* deprecate *all* global operator overloads. Global
operators can still be implemented as they have been in Swift. So if you
have a concrete type like `struct Matrix`, you can still define at the
global level `func +(lhs: Matrix, rhs: Matrix) -> Matrix`.

What's being deprecated is the current syntax used to define operator
requirements inside protocols (by making the functions static) and the
manner by which subtypes conform (ditto, through static methods instead of
global functions).

OK, I guess the unclear part is when you talk about "an addition that
specifically provides improvements for protocol operator requirements."
This is not just an addition; it's intended to completely replace the
protocol operator syntax.

2) The method signatures in the examples are not up to date with the
current
Swift 3 syntax. For example:

protocol Equatable {
  static func ==(lhs: Self, rhs: Self) -> Bool
}

should be:

protocol Equatable {
  static func ==(_ lhs: Self, _ rhs: Self) -> Bool
}

Unless I'm mistaken, from looking at the Swift 3 branch of stdlib, the
syntax changes don't appear to apply to operator functions. Since they are
a special case that don't have argument labels, it wouldn't make sense to
require them (or rather, the placeholders) here.

I don't agree with this.

Operators are called like this:

x = y + z

Of course it doesn't make sense to have parameter labels there.

But the ones inside the protocol are not operators. They are methods,
and are called like methods. They happen to have funny names, but they are
still methods, and are called like this:

x = T.+(y, z)

In this case not only it makes sense for the parameters to have labels,
but making them behave differently from normal methods would be
inconsistent, and a step backwards from all the progress that has been made
in Swift 3 on that front.

What I'm saying is, if you look at the Swift 3 branch of stdlib, global
operator functions still do not have argument labels. Picking one at
random:
https://github.com/apple/swift/blob/swift-3.0-branch/stdlib/public/core/String.swift#L329

If you're arguing that those functions should be forced to include `_`
placeholders, that's fine, but it's not accurate to say that the way
they're written in this proposal is a step backwards from all the progress
made in Swift 3. It is *consistent* with the way global operator functions
are currently declared in Swift 3.

If it changes there, then it should change here as well. But they should
be the same, and making that change for global operator functions is not
part of the scope of this proposal.

I'm not talking about the global operator functions; I'm talking about
the methods inside the protocol, which are methods and are called like
methods; they are not operators.

Thanks for expressing this so clearly. I'm of the same feeling but fumbled
the communication of it.

On re-evaluation, I wonder if this proposal as it is would be a
sufficiently large improvement. It's essentially permitting the use of
characters reserved for operators in static method names, but it adds a set
of somewhat inconsistent rules for how those functions are to be declared
and called. As mentioned earlier, `T....(x, y)` looks rather unfortunate,
and since automatic trampolines are out of scope, I wonder if what we have
currently (naming static methods using words) is altogether that bad. Maybe
we could just standardize those names and be done with it; on a cursory
look, that seems to be Rust's approach.

Personally, I find Rust’s approach a bit ugly. And Tony makes a very good
point in the proposal that using words requires us to learn the word
associated with each operator.

Yeah, also good points. Can I propose maybe another approach?

T.operator(+, a, b)
T.operator(prefix: ++, a)
T.operator(postfix: ++, a)
···

On Wed, May 18, 2016 at 3:56 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On May 18, 2016, at 3:48 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
On Wed, May 18, 2016 at 3:37 PM, Nicola Salmoria via swift-evolution < > swift-evolution@swift.org> wrote:

On Wed, May 18, 2016 at 10:27 PM, Tony Allevato <allevato@google.com> >> wrote:

On Wed, May 18, 2016 at 1:00 PM Nicola Salmoria < >>> nicola.salmoria@gmail.com> wrote:

On Wed, May 18, 2016 at 8:03 PM, Tony Allevato <allevato@google.com> >>>> wrote:

On Wed, May 18, 2016 at 10:02 AM Nicola Salmoria via swift-evolution < >>>>> swift-evolution@swift.org> wrote:

I noted some concerns about this proposal not including automatic
trampolines. However, if we are not going to be able to make a breaking
change like this in the standard library after Swift 3 I do think it is
important to give this proposal significant consideration even without
them. Automatic trampolines can be added later but we may not have the
opportunity to fix the standard library protocols later.

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols
are
effectively just normal methods (apart from their names), we just
need to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are
other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less
clear than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one
would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static"
for consistency. As you mention, there's a stronger argument to be made for
assignment operators to have "left hand side is the receiver" semantics
than there are for standard infix operators, but from a consistency point
of view (and ease of learning), I think having everything static and the
signatures of the static operators matching those of the global operators
is preferable.

I think this would better be left as a choice to the author of the
protocol. There doesn't seem to be any technical reason to place this
restriction.

(Which is also why, as I mentioned in a previous reply, I would be
open to dropping the prefix/postfix keyword and making it an argument label
instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the
dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators
with
problematic names would need to use different names. Arguably, the
only
cases where one would really want to use methods with operator names
is for
arithmetical operators. Custom operators like ... are better
expressed as
methods with more significant names.

If there is a strong case where an operator is better implemented as a
global operator and a named method, this proposal still allows that, since
it's not deprecating all global operator definitions. A protocol could
certainly have a requirement that is a named method, and provide a global
generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say
there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator
name
won't help. If it can, why put limits? There could be other creative
uses of
such names, which we would be ruling out for no particular reason.
This is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function
whose name is the same as a defined operator should conform to the
requirements (such as argument count) of that operator. It's certainly
worth discussion, though! With that being said, it may be easier on users
to "rule something out" now and open it up later if need be, rather than to
leave it open for people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with
the same name and different signature, which Swift allows without problems.
Again, I think this is a choice that the author of the protocol should
make, and there doesn't seem to be any technical reason to require
otherwise.

7) Automatic generation of trampoline functions is out of scope so
I'm not
going to talk much about it, I only want to mention that it would
make sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin,
cos, etc.
It could make sense to give them the same treatment as operators,
declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only
from
global space to a type, but also from one type to another type, or
even for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a
change to
Swift?

Absolutely. The handling of operators in protocols has been one of
the worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar
feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much
less
advanced.

> * How much effort did you put into your review? A glance, a quick
reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant
threads on
the mailing list.

--
Nicola

_______________________________________________
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

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

Personally, I find Rust’s approach a bit ugly. And Tony makes a very good point in the proposal that using words requires us to learn the word associated with each operator.

Right—and in addition to the cognitive overload of knowing (and remembering) the word associated with each operator, it introduced bloat in those interfaces. End users of such an interface may question why there is a named method for that operator, and whether the named method and the operator function differently.

Likewise, in many cases (such as arithmetic operations), it seems contrived to come up with names for an operator where that operator is already a term of art that can express the idea better than the words can.

I noted some concerns about this proposal not including automatic trampolines. However, if we are not going to be able to make a breaking change like this in the standard library after Swift 3 I do think it is important to give this proposal significant consideration even without them. Automatic trampolines can be added later but we may not have the opportunity to fix the standard library protocols later.

I wish I had been able to keep automatic trampolines in—I thought it was the "killer feature" that brings the whole proposal together. (Hey core team, you can still change your mind! :)

That being said, I feel that the named-method approach is a huge step in the wrong direction and this proposal is strong enough without them to improve other existing ones, such as FloatingPoint, and would go a long way toward cleaning up the language in other extremely common cases (like anything that conforms to Equatable).

Tony (and core team), do you have any thoughts on the specific concerns I brought up?

Imagine this:

protocol P {
    static func ++++(lhs: Self, rhs: Self) -> Self
}
func ++++ <T: P>(lhs: T, rhs: T) -> T {
    return T.++++(lhs, rhs)
}

protocol Q {
    static func ++++(lhs: Self, rhs: Self) -> Self
}
func ++++ <T: Q>(lhs: T, rhs: T) -> T {
    return T.++++(lhs, rhs)
}

struct S: P, Q {
    static func ++++(lhs: Self, rhs: Self) -> Self {
        // ...
    }
}

let s1 = S()
let s2 = S()
let s3 = s1 ++++ s2 // compiler error, both trampolines are an equally good match, resulting in ambiguity

// we have to add the following to resolve the ambiguity:

func ++++(lhs: S, rhs: S) -> S {
    return S.++++(lhs, rhs)
}

You could argue that this is a contrived example and is unlikely to happen in real code. My point is partly that it’s a regression from current state and partly that it is very likely to be confusing if people run into it.

···

On May 18, 2016, at 4:06 PM, Tony Allevato <allevato@google.com> wrote:
On Wed, May 18, 2016 at 1:56 PM Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

3) As has already been noted by many others, the suggested syntax for
prefix/postfix operators is overcomplicated. The proposal is:

// These are deprecated, of course, but used here just to serve as an
// example.
static prefix func ++(_ value: inout Self) -> Self
static postfix func ++(_ value: inout Self) -> Self

We don't need that. Since the 'operators' declared inside protocols are
effectively just normal methods (apart from their names), we just need to
name the parameters accordingly:

static func ++(prefix value: inout Self) -> Self
static func ++(postfix value: inout Self) -> Self

4) I don't agree with the request to limit to static methods for the
operator implementations.
I support this for symmetrical binary operators like +, but there are other
operators like += that seem to work better with members. That is, the
proposed declaration:

static func +=(_ lhs: inout Self, _ rhs: Self)

is more similar to the global += operator definition, but is less clear than:

mutating func +=(_ rhs: Self)

this is apparent also at the call site. With the proposed syntax, one would
need to do:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    T.+=(lhs, rhs)
}

while with a member function this would read more naturally as:

func +=<T: Foo>(_ lhs: inout T, _ rhs: T) {
    lhs.+=(rhs)
}

I considered this, but eventually settled on "everything is static" for consistency. As you mention, there's a stronger argument to be made for assignment operators to have "left hand side is the receiver" semantics than there are for standard infix operators, but from a consistency point of view (and ease of learning), I think having everything static and the signatures of the static operators matching those of the global operators is preferable.

I think this would better be left as a choice to the author of the protocol. There doesn't seem to be any technical reason to place this restriction.

(Which is also why, as I mentioned in a previous reply, I would be open to dropping the prefix/postfix keyword and making it an argument label instead, in both contexts.)

5) the proposal mentions the open question of ambiguities between the dot
syntax to access methods and operators whose name starts with a dot.
This seems to be a real issue: I don't think

return T....(minimum, maximum)

looks any good, even if the compiler was able to parse it.

However, this just means that the methods used to implement operators with
problematic names would need to use different names. Arguably, the only
cases where one would really want to use methods with operator names is for
arithmetical operators. Custom operators like ... are better expressed as
methods with more significant names.

If there is a strong case where an operator is better implemented as a global operator and a named method, this proposal still allows that, since it's not deprecating all global operator definitions. A protocol could certainly have a requirement that is a named method, and provide a global generic operator that calls it.

6) It seems somewhat arbitrary to restrict method names to match an
operator, nor to put requirements on the function signature. I'd say there
are two cases, either the compiler can handle a method name that uses
special characters, or it can't. If it can't, matching an operator name
won't help. If it can, why put limits? There could be other creative uses of
such names, which we would be ruling out for no particular reason. This is
something that seems better left to the author of the protocol.

IMO, to reduce potential confusion, I would argue that a function whose name is the same as a defined operator should conform to the requirements (such as argument count) of that operator. It's certainly worth discussion, though! With that being said, it may be easier on users to "rule something out" now and open it up later if need be, rather than to leave it open for people to use and decide it needs to be closed later.

This doesn't seem different to me from having multiple functions with the same name and different signature, which Swift allows without problems. Again, I think this is a choice that the author of the protocol should make, and there doesn't seem to be any technical reason to require otherwise.

7) Automatic generation of trampoline functions is out of scope so I'm not
going to talk much about it, I only want to mention that it would make sense
to consider making such a feature as general as possible, instead of
focusing exclusively on operators.

For example, think of the common mathematical functions like sin, cos, etc.
It could make sense to give them the same treatment as operators, declaring
them as part of the FloatingPoint protocol but preserving the global
functions too.
It might even make sense to be able to create trampolines not only from
global space to a type, but also from one type to another type, or even for
all methods of a type (e.g. when boxing a value inside another type).

> * Is the problem being addressed significant enough to warrant a change to
Swift?

Absolutely. The handling of operators in protocols has been one of the worst
pain points in my use of Swift.

> * Does this proposal fit well with the feel and direction of Swift?

Yes; it significantly increases clarity and consistency.

> * If you have used other languages or libraries with a similar feature,
how do you feel that this proposal compares to those?

I only have experience with C++ operator overloading, which is much less
advanced.

> * How much effort did you put into your review? A glance, a quick reading,
or an in-depth study?

An in-depth study of the proposal, and I read all the relevant threads on
the mailing list.

--
Nicola

_______________________________________________
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

_______________________________________________
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

They're methods that have names that are operators, so the proposal treats
them like other functions that have names that are operators.

In Swift 3, there are two types of global functions:
1) Functions with names that are not operators, which have mandatory
argument labels; e.g., `func abs(_ x: Int)`
2) Functions with names that are operators, which mandatorily *lack*
argument labels; e.g., `func +(lhs: Int, rhs: Int)`

Static methods are just functions that are attached to a class type; why
introduce more inconsistency by requiring labels on them as methods but not
as global functions? This way, an operator as a static requirement of a
protocol looks just like the global operator that matches it.

Similarly, if you pass a bare operator into another algorithm (like
`reduce`), you call it without any argument labels, so there are already
situations where calling an operator in that way (even though it's
indirect) omits the labels.

I can't entirely disagree that there is inconsistency here; but IMO the
inconsistency is with operator functions *in general* lacking labels, not
with the treatment of them proposed here.

···

On Wed, May 18, 2016 at 1:37 PM Nicola Salmoria <nicola.salmoria@gmail.com> wrote:

On Wed, May 18, 2016 at 10:27 PM, Tony Allevato <allevato@google.com> > wrote:

What I'm saying is, if you look at the Swift 3 branch of stdlib, global
operator functions still do not have argument labels. Picking one at
random:
https://github.com/apple/swift/blob/swift-3.0-branch/stdlib/public/core/String.swift#L329

If you're arguing that those functions should be forced to include `_`
placeholders, that's fine, but it's not accurate to say that the way
they're written in this proposal is a step backwards from all the progress
made in Swift 3. It is *consistent* with the way global operator functions
are currently declared in Swift 3.

If it changes there, then it should change here as well. But they should
be the same, and making that change for global operator functions is not
part of the scope of this proposal.

I'm not talking about the global operator functions; I'm talking about the
methods inside the protocol, which are methods and are called like methods;
they are not operators.