[swift-evolution] [Proposal] Improving operator requirements in protocols

Tony Allevato allevato at google.com
Mon May 2 15:41:27 CDT 2016


On Mon, May 2, 2016 at 1:25 PM Dave Abrahams via swift-evolution <
swift-evolution at swift.org> wrote:

>
> on Mon May 02 2016, Tony Allevato <swift-evolution at swift.org> wrote:
>
> > Open issue: Class types and inheritance
> >
> > While this approach works well for value types, these static operators
> may not
> > work as expected for class types when inheritance is involved, and more
> work may
> > be needed here.
> >
> > We can currently model the behavior we'd like to achieve by using a
> named eq
> > method instead of the operator itself. (Note that we are not proposing
> that the
> > function be named eq in the final design; this was done simply to
> perform the
> > experiment with today's compiler.) Then we implement both the new method
> and the
> > current == operator and compare their behaviors. For example:
> >
> > protocol ProposedEquatable {
> >   static func eq(lhs: Self, _ rhs: Self) -> Bool
> > }
> >
> > class Base: ProposedEquatable, Equatable {
> >   static func eq(lhs: Base, _ rhs: Base) -> Bool {
> >     print("Base.eq")
> >     return true
> >   }
> > }
> > func ==(lhs: Base, rhs: Base) -> Bool {
> >   print("==(Base, Base)")
> >   return true
> > }
> >
> > class Subclass: Base {
> >   static func eq(lhs: Subclass, _ rhs: Subclass) -> Bool {
> >     print("Subclass.eq(Subclass, Subclass)")
> >     return true
> >   }
> > }
> > func ==(lhs: Subclass, rhs: Subclass) -> Bool {
> >   print("==(Subclass, Subclass)")
> >   return true
> > }
> >
> > func eq<T: ProposedEquatable>(lhs: T, _ rhs: T) -> Bool {
> >   return T.eq(lhs, rhs)
> > }
> >
> > let x = Subclass()
> > let y = Subclass()
> > let z = y as Base
> >
> > eq(x, y)  // prints "Base.eq"
> > eq(x, z)  // prints "Base.eq"
> >
> > x == y    // prints "==(Subclass, Subclass)"
> > x == z    // prints "==(Base, Base)"
> >
> > The result of eq(x, y) was a bit surprising, since the generic argument
> T is
> > bound to Subclass and there should be no dynamic dispatch at play there.
> (Is the
> > issue that since Base is the class explicitly conforming to
> ProposedEquatable,
> > this is locking in Self being bound as Base, causing that overload to be
> found
> > in the compiler's search? Or is this a bug?)
> >
> > An attempt was also made to fix this using dynamic dispatch, by
> implementing eq
> > as a class method instead of astatic method:
> >
> > protocol ProposedEquatable {
> >   static func eq(lhs: Self, _ rhs: Self) -> Bool
> > }
> >
> > class Base: ProposedEquatable, Equatable {
> >   class func eq(lhs: Base, _ rhs: Base) -> Bool {
> >     print("Base.eq")
> >     return true
> >   }
> > }
> > func ==(lhs: Base, rhs: Base) -> Bool {
> >   print("==(Base, Base)")
> >   return true
> > }
> >
> > class Subclass: Base {
> >   override class func eq(lhs: Base, _ rhs: Base) -> Bool {
> >     print("Subclass.eq(Base, Base)")
> >     return true
> >   }
> >   class func eq(lhs: Subclass, _ rhs: Subclass) -> Bool {
> >     print("Subclass.eq(Subclass, Subclass)")
> >     return true
> >   }
> > }
> > func ==(lhs: Subclass, rhs: Subclass) -> Bool {
> >   print("==(Subclass, Subclass)")
> >   return true
> > }
> >
> > func eq<T: ProposedEquatable>(lhs: T, _ rhs: T) -> Bool {
> >   return T.eq(lhs, rhs)
> > }
> >
> > let x = Subclass()
> > let y = Subclass()
> > let z = y as Base
> >
> > eq(x, y)  // prints "Subclass.eq(Base, Base)"
> > eq(x, z)  // prints "Base.eq"
> >
> > x == y    // prints "==(Subclass, Subclass)"
> > x == z    // prints "==(Base, Base)"
> >
> > This helped slightly, since at least it resulting in a method on the
> expected
> > subclass being called, but this still means that anyone implementing this
> > operator on subclasses would have to do some casting, and it's awkward
> that
> > subclasses would be expected to write its operator in terms of the
> conforming
> > base class.
> >
> > It should also be noted (code not provided here) that using instance
> methods
> > does not solve this problem, presumably for the same dispatch-related
> reasons
> > that the class methods called the version with Base arguments.
>
> Do we not have essentially all the same problems with classes, even with
> today's Equatable?  If not, what are the differences?
>

We do have some problems today, such as above where using `==` on a `(Base,
Subclass as Base)` pair ends up calling `==(Base, Base)` because we lack
multiple dispatch. What surprised me though was that the `eq` call between
two `Subclass` instances passed to the trampoline operator ended up calling
`Base.eq`. I would have expected `Subclass.eq` to be called there since the
generic argument `T` was bound to `Subclass`. Today, a non-generic
`==(Subclass, Subclass)` operator *does* do the right thing.

I mainly called it out because the problems we have with `==` today are
slightly different than the problems encountered when testing out the new
model. If the decision is "operators with Self constraints are
fundamentally hard with classes and we don't necessarily expect them to
work consistently", then that's fine, but I wanted to make sure it wasn't a
hole in the proposal.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160502/99667c67/attachment.html>


More information about the swift-evolution mailing list