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

Hello Swift community,

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

  swift-evolution/0091-improving-operators-in-protocols.md at master · apple/swift-evolution · GitHub

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at:

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and contribute to the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?
  * Is the problem being addressed significant enough to warrant a change to Swift?
  * Does this proposal fit well with the feel and direction of Swift?
  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

  swift-evolution/process.md at master · apple/swift-evolution · GitHub

Thank you,

-Chris Lattner
Review Manager

  * What is your evaluation of the proposal?

+1. Improved type checker performance for operator overloads alone is enough for me. Improved locality of implementation is the cherry on top.

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

Absolutely. This means protocols can be reorganized around operators without cluttering the global space space.

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

Yes.

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

The only other language I can think of that does this is C++.

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

Talked to Doug about it this morning.

···

On Jul 7, 2016, at 10:54 AM, Chris Lattner <clattner@apple.com> wrote:

Hello Swift community,

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

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

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at:

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and contribute to the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?
  * Is the problem being addressed significant enough to warrant a change to Swift?
  * Does this proposal fit well with the feel and direction of Swift?
  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

  https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager

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

* What is your evaluation of the proposal?
+1
        * Is the problem being addressed significant enough to warrant a
change to Swift?
Yes
        * Does this proposal fit well with the feel and direction of Swift?
Yes
        * If you have used other languages or libraries with a similar
feature, how do you feel that this proposal compares to those?
Not exactly, no.
        * How much effort did you put into your review? A glance, a quick
reading, or an in-depth study?
I've followed the various pitches and read this proposal through.

A few questions:

1. What is universal lookup? How is this different than the compiler having to search a crowded namespace? I'm really trying to understand how this is making it easier for the type checker.

2. If operators are a special case, why not replace static/class func with an "operator" keyword? Requiring static/class seems like an implementation detail to get this universal lookup.

3. Will the static vs class distinction ever be fixed or cleaned up? Isn't there a way to share one keyword for this same feature across all types?

Regardless, I'm still giving this a +1.

I'm just hoping we can:
- reduce some cognitive burden
- add a little character

Brandon

···

On Jul 7, 2016, at 1:54 PM, Chris Lattner <clattner@apple.com> wrote:

Hello Swift community,

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

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

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at:

   https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and contribute to the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

   * What is your evaluation of the proposal?
   * Is the problem being addressed significant enough to warrant a change to Swift?
   * Does this proposal fit well with the feel and direction of Swift?
   * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
   * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

   https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager

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

  * What is your evaluation of the proposal?

+1. This proposal improves clarity by allowing operator implementations to be declared inside the relevant type (or an extension of it). It also improves implementation as outlined in the proposal. Both are significant steps forward.

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

Yes. I always thought it was unfortunate that operators have to be global functions.

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

Very much so.

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

The closest is C++.

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

In depth study with the earlier proposal, discussion and review. Quick reading this time around. The new proposal is much better and resolves the things I did not like about the original version.

···

More information about the Swift evolution process is available at

  https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager

_______________________________________________
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 ]

I definitely think this is an improvement over the last version! Nice work, Tony and Doug.

I am a little confused about the implementation, though. The proposal says this:

Instead, Swift should always perform operator lookup universally such that it sees all operators defined at either module scope or within a type/extension of a type. This gives us the syntactic improvements immediately and the natural Swift thing of defining your functionality within the type or an extension thereof just works.

and then later says

Therefore, we can achieve the performance improvements by making that insight 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.

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 as if users had declared trampoline operators, but it's automatic.

How do we know if an operator function implements a protocol requirement? What happens when an operator function implements a protocol requirement, but is also more general than that? And if we do find the implementation in the protocol, what conformance do we use to invoke the function when the types involved aren’t all 'Self'?

I still prefer the rule that says we perform lookup into the left type and the right type, then fall back to top-level scope for backwards compatibility.

Separately from the lookup rules, I’m still unhappy with the class problem. The proposal states this:

We expect classes to implement the static operators in the protocol using `class` methods instead of `static` methods, which allows subclases to override them.

However, if lookup only finds the method in the protocol, it’s unclear whether this will call a conforming class's method, a static type’s method, or a dynamic type’s method; if it’s not the last, it’s hardly an “override”. I maintain that this is the wrong behavior for any class hierarchy that does include heterogeneous operations, including "assignment operators, operators for chaining tasks, DSLs for constraint systems, etc” (me, from last time).

More from last time:

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

…and I have to say I still feel that way. It’s not clear how much of a performance win we’ll get, and it’s not clear these are the right semantics, and it is clear that operators interact poorly with classes.

Jordan

P.S. The proposal also has this line:

non-static operator method syntax be deprecated in Swift 2 and removed in Swift 3

which should be updated in one way or another.

What is your evaluation of the proposal?

+1

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

Yes, the current situation of defining a protocol required operator
function globally is potentially confusing and feels inconsistent.

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

Yup.

How much effort did you put into your review? A glance, a quick reading, or

an in-depth study?

A reading.

One thing that's not completely clear to me: if you implement the operator
function as a class method and override it in a subclass, in what situation
would the overridden version be called?

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

I definitely think this is an improvement over the last version! Nice work, Tony and Doug.

I am a little confused about the implementation, though. The proposal says this:

Instead, Swift should always perform operator lookup universally such that it sees all operators defined at either module scope or within a type/extension of a type. This gives us the syntactic improvements immediately and the natural Swift thing of defining your functionality within the type or an extension thereof just works.

and then later says

Therefore, we can achieve the performance improvements by making that insight 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.

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 as if users had declared trampoline operators, but it's automatic.

How do we know if an operator function implements a protocol requirement?

Well, we can track it explicitly in the modules that define the protocol and that define conformances of specific types to the protocol. Alternatively, we try the protocol requirements *first*. Once we’ve inferred the ‘Self’ type of the protocol (which is part of trying the protocol requirement), we can look up a conformance and the witness to see which witnesses should no longer be considered.

What happens when an operator function implements a protocol requirement, but is also more general than that?

*Right now*, it’s only really possible when you’re using a global generic operator, because we require exact type matches between requirements and witnesses. If/when we allow the witness to be a supertype of the requirement, you’ll start to see more of the semantic effects of this model, because shadowing the witness with the requirement can reject code that is well-formed now.

That’s why the shadowing behavior needs to be part of the semantic model. Implementation will let us settle the exact details so we can state those semantics more precisely.

And if we do find the implementation in the protocol, what conformance do we use to invoke the function when the types involved aren’t all 'Self’?

If there isn’t a reference to ‘Self’, type inference for the use of the protocol requirement will fail.

I still prefer the rule that says we perform lookup into the left type and the right type, then fall back to top-level scope for backwards compatibility.

We thought about it a lot, and it’s not implementable in a way that’s consistent with type inference, because one of the left or right types might not be known yet, and you also need to consider the context type for something like, e.g.,

  let x: UInt = 1 + 2

Separately from the lookup rules, I’m still unhappy with the class problem. The proposal states this:

We expect classes to implement the static operators in the protocol using `class` methods instead of `static` methods, which allows subclases to override them.

However, if lookup only finds the method in the protocol, it’s unclear whether this will call a conforming class's method, a static type’s method, or a dynamic type’s method; if it’s not the last, it’s hardly an “override”. I maintain that this is the wrong behavior for any class hierarchy that does include heterogeneous operations, including "assignment operators, operators for chaining tasks, DSLs for constraint systems, etc” (me, from last time).

More from last time:

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

…and I have to say I still feel that way. It’s not clear how much of a performance win we’ll get, and it’s not clear these are the right semantics, and it is clear that operators interact poorly with classes.

This is a good point, and one that’d I’d missed from the previous discussions. Left-bias is probably defensible, but I’m happy to do the conservative thing here: require the operator to be ‘final’. That’s no worse than what we have today—you have to delegate to something that’s explicitly dynamically dispatched based on whichever of the types you choose—and leaves open the possibility of loosening the rule in the future.

Jordan

P.S. The proposal also has this line:

non-static operator method syntax be deprecated in Swift 2 and removed in Swift 3

which should be updated in one way or another.

Sure.

  - Doug

···

On Jul 12, 2016, at 10:35 AM, Jordan Rose <jordan_rose@apple.com> wrote:

What is your evaluation of the proposal?

+1

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

Yes, the current situation of defining a protocol required operator
function globally is potentially confusing and feels inconsistent.

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

Yup.

How much effort did you put into your review? A glance, a quick reading,

or an in-depth study?

A reading.

One thing that's not completely clear to me: if you implement the operator
function as a class method and override it in a subclass, in what situation
would the overridden version be called?

(Using a binary operator as an example) Since the signature of the operator
is (Self, Self), you would have a situation like the following:

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

class A: Equatable {
  class func ==(lhs: A, rhs: A) -> Bool { ... }
}

class B: A {
  class func ==(lhs: B, rhs: B) -> Bool { ... }
}

There is actually no overriding happening here, because the argument types
are different.

So,

    let a = A()
    let b = B()
    let b_as_a: A = B()

    b == B() // should call B.==
    a == B() // should call A.==, because the only match is by upcasting B
to A
    b == b_as_a // should call A.==, because the dispatch is done
statically

The latter of those may seem surprising, but it's not a regression—the
current global operators would do the same (and Swift would need
multiple-dispatch to solve this).

That being said, it's likely that you would want to be able to call A's ==
implementation as part of B's. And it just occurs to me that we don't need
to support "super" to do this—the solution is rather elegant:

    class func ==(lhs: B, rhs: B) -> Bool {
      guard lhs as A == rhs as A else {
        return false
      }
      return lhs.someOtherProperty == rhs.someOtherProperty
    }

By explicitly casting the arguments to their superclass, the operator type
dispatch (which is based on the static types of the arguments) will call
the correct implementation.

···

On Thu, Jul 7, 2016 at 11:37 AM Daniel Resnick via swift-evolution < swift-evolution@swift.org> wrote:

_______________________________________________
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
]

I definitely think this is an improvement over the last version! Nice
work, Tony and Doug.

I *am* a little confused about the implementation, though. The proposal
says this:

Instead, Swift should always perform operator lookup universally such that
it sees all operators defined at either module scope or within a
type/extension of a type. This gives us the syntactic
improvements immediately and the natural Swift thing of defining your
functionality within the type or an extension thereof just works.

and then later says

Therefore, we can achieve the performance improvements by making that
insight 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.

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 as if users had
declared trampoline operators, but it's automatic.

How do we know if an operator function implements a protocol requirement?

Well, we can track it explicitly in the modules that define the protocol
and that define conformances of specific types to the protocol.
Alternatively, we try the protocol requirements *first*. Once we’ve
inferred the ‘Self’ type of the protocol (which is part of trying the
protocol requirement), we can look up a conformance and the witness to see
which witnesses should no longer be considered.

What happens when an operator function implements a protocol requirement,
but is also more general than that?

*Right now*, it’s only really possible when you’re using a global generic
operator, because we require exact type matches between requirements and
witnesses. If/when we allow the witness to be a supertype of the
requirement, you’ll start to see more of the semantic effects of this
model, because shadowing the witness with the requirement can reject code
that is well-formed now.

That’s why the shadowing behavior needs to be part of the semantic model.
Implementation will let us settle the exact details so we can state those
semantics more precisely.

And if we do find the implementation in the protocol, what conformance do
we use to invoke the function when the types involved aren’t all 'Self’?

If there isn’t a reference to ‘Self’, type inference for the use of the
protocol requirement will fail.

I still prefer the rule that says we perform lookup into the left type and
the right type, then fall back to top-level scope for backwards
compatibility.

We thought about it a lot, and it’s not implementable in a way that’s
consistent with type inference, because one of the left or right types
might not be known yet, and you also need to consider the context type for
something like, e.g.,

let x: UInt = 1 + 2

Separately from the lookup rules, I’m still unhappy with the class
problem. The proposal states this:

We expect classes to implement the static operators in the protocol using
`class` methods instead of `static` methods, which allows subclases to
override them.

However, if lookup only finds the method in the protocol, it’s unclear
whether this will call a conforming class's method, a static type’s method,
or a dynamic type’s method; if it’s not the last, it’s hardly an
“override”. I maintain that this is the wrong behavior for any class
hierarchy that *does* include heterogeneous operations, including
"assignment operators, operators for chaining tasks, DSLs for constraint
systems, etc” (me, from last time).

More from last time:

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

…and I have to say I still feel that way. It’s not clear how much of a
performance win we’ll get, and it’s not clear these are the right
semantics, and it *is* clear that operators interact poorly with classes.

This is a good point, and one that’d I’d missed from the previous
discussions. Left-bias is probably defensible

Jumping in where I don't really have any business: left-bias for all infix
operators, or left-bias for left-associative operators and right-bias for
right-associative operators?

···

On Wed, Jul 13, 2016 at 1:14 PM, Douglas Gregor via swift-evolution < swift-evolution@swift.org> wrote:

On Jul 12, 2016, at 10:35 AM, Jordan Rose <jordan_rose@apple.com> wrote:

, but I’m happy to do the conservative thing here: require the operator to
be ‘final’. That’s no worse than what we have today—you have to delegate to
something that’s explicitly dynamically dispatched based on whichever of
the types you choose—and leaves open the possibility of loosening the rule
in the future.

Jordan

P.S. The proposal also has this line:

non-static operator method syntax be *deprecated* in Swift 2 and *removed* in
Swift 3

which should be updated in one way or another.

Sure.

- Doug

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