1

The following code was mentioned at WWDC 2015:

protocol Drawable {
  func isEqualTo(other: Drawable) -> Bool
  func draw()
}

extension Drawable where Self : Equatable {
  func isEqualTo(other: Drawable) -> Bool {
    if let o = other as? Self { return self == o }
    return false
  } 
}

I'm a little confused on this whole protocol extension thing. Why would they have isEqualTo(other: Drawable) -> Bool in the Drawable protocol and then only extend when self is equatable? Why should isEqualTo be a required method for all Drawable objects? From my view, if a new class/struct hasn't implemented Equatable, the objects don't have the capability to be logically checked for equality, so they couldn't implement an equatable method. I think it would make more sense to have it be an optional implementation. Where is the fault in my logic?

nhgrif
  • 58,130
  • 23
  • 123
  • 163
rb612
  • 4,453
  • 2
  • 19
  • 49
  • I cover this in my answer, but the short answer is that strictly Objective-C classes could conform to `Drawable` from the Objective-C side without any Swift code, but they could not possibly conform to `Equatable` without you writing a `==` method for *every* Objective-C object you wanted conforming to `Drawable`. [See this related answer](http://stackoverflow.com/a/30929046/2792531)... – nhgrif Jun 25 '15 at 00:26
  • This is _completely_ explained in the WWDC 2015 video on protocol extensions. – matt Jun 25 '15 at 00:32
  • I watched the video @matt (and the last 10 minutes 3 times) and that's where I got this code from. I just started learning only a few months ago, and 90% of the lecture made sense to me. However, I don't understand why he made it required to have the isEqualTo method. Sure, you can extend the extension for only Equatable objects, but why have it required for all drawable objects that may or may not be equatable? – rb612 Jun 25 '15 at 00:41
  • For the reason that he gives in the video. Without protocol extensions, a generic can only define equatability for adopters of a protocol that are _typed_ as of the same class / struct. With a protocol extension, we can define equatability more generally for _any_ pair of adopters of a protocol, even if they are typed as the protocol. – matt Jun 25 '15 at 00:49
  • @matt right that does make more sense to me now. Thank you for clearing it up. So then my question remains though: for drawable objects that do **not** implement equatable, why do we force them to have an `isEqualTo` method? The extension won't cover non-equatable conforming drawables, so conforming classes and structs falling in this category are **required** to implement a method similar to equatable, `isEqualTo`, even though they don't conform to `equatable`. It just doesn't make sense to me why we wouldn't just leave all non-equatable drawable objects out of the picture. – rb612 Jun 25 '15 at 00:55
  • Because who ever is using `Drawable` needs to compare the `Drawable` things to determine if they're equal, for whatever reason. It's almost certainly for Objective-C objects. Perhaps check out this question & answer: http://stackoverflow.com/a/30929046/2792531 – nhgrif Jun 25 '15 at 00:57
  • 1
    I've given an answer explaining the point that the video is trying to make (without going into the actual implementation details). But really I suggest you just watch the video again. – matt Jun 25 '15 at 01:00

2 Answers2

3

The problem being solved is a limitation of generics.

Let's say we have a Bird struct and Insect struct. A generic equatable lets us define == where the actual object types are the same type. So we can make Bird adopt Equatable so that if we have b1 and b2 both typed as Bird we can decide whether they are equal. And we can make Insect adopt Equatable so that if we have i1 and i2 both typed as Insect we can decide whether they are equal.

But now suppose both Bird and Insect adopt the Flier protocol. You cannot make Flier adopt Equatable, because there's a limitation in how generics work. So if two objects are typed as Flier, you have no way of implementing equatability for them.

The video demonstrates that protocol extensions solve this problem. With a protocol extension on Flier, you can define a different method that compares two Fliers and decides whether they are equal - namely, by deciding first whether they are of the same class and then applying ==. Thus you can make sense of equatability for a Flier (a protocol).

matt
  • 447,615
  • 74
  • 748
  • 977
  • This example explains it in more understandable terms than my answer. This made it make even more sense to me (and I thought I understood it decently well). – nhgrif Jun 25 '15 at 01:01
  • 2
    @ngrif The video is still somewhat mind-blowing, as advertised, but it does help to keep in mind the homogeneous / heterogenous distinction he makes at the outset. If you've ever tried to make a protocol adopt Equatable you will know _exactly_ what he's talking about, because you will have hit this actual wall. – matt Jun 25 '15 at 01:02
  • Matt, thank you so much. This is great, and really helped me understand it better. The part I have my mind wrapped around is really more of a general question, and I just want to have clarification on it. You know how he says in the video, "If the implementers of drawable want to implement a heterogeneous case, go ahead." So from the way he says it, it seems like `isEqualTo` can be implemented or not - it's not *required*. But in the protocol, it's a required function. And is that just because we have to have a way to check equality for all drawable objects, not just equatables? – rb612 Jun 25 '15 at 01:07
  • IIRC this is a protocol extension. Well, a protocol extension is not a protocol. It does not have "requirements". This is not a requirement; it's a method. It's a method that is simply injected into its adopters. They can implement it themselves, but if they don't, they inherit the injected method. – matt Jun 25 '15 at 01:11
  • OH okay - there that's perfect. and I just went back to a part I mentally skipped over where he said "always make types equatable. why? because I said so." and that explains why it's a required method in the protocol: because all drawable objects must be equatable, and that's explained in another WWDC lecture. cased closed. Thanks @matt and @nhgrif! – rb612 Jun 25 '15 at 01:12
1

I can only guess as to why isEqualTo: is a required method for the drawable protocol. Perhaps so that whatever is drawing these things never wastes time drawing the same thing twice?

I can comment on the rest of it however.

Equatable is a Swift protocol (not available in Objective-C) which requires that there is a == operator defined for the type.

In Objective-C, there is no operator overloading. Moreover, in Objective-C, using == to compare objects simply compares their pointers. If the objects are the same object (same memory location), == returns true. If we want to see if the objects are different objects but still considered equal, we must use isEqualTo:. This is a method defined by NSObject and the default implementation simply returns the result of == comparison. But classes tend to override this.

In Swift, == has different behavior. In Swift, == returns behaves similarly to how we expect the isEqualTo: method to behave in Objective-C. That's because Swift has the === operator for comparing references. === returns true if these objects are the same (same memory location), but == is a custom implemented method that determines whether the objects are considered equal even if they are in different memory locations.

So I'm guessing the Drawable protocol has Objective-C classes in mind when it declares isEqualTo: as one of its required methods.

We could alternatively write the Drawable protocol as such:

protocol Drawable: Equatable {
    func draw()
}

From a strictly Swift perspective, this is a roughly equivalent protocol definition. But this means that whoever is using Drawable objects expects to compare them with == in Swift rather than isEqualTo:. And moreover, this means if we want to use any Objective-C defined objects with the protocol, now we must implement a custom == function for each of them, which most likely looks like this:

func == (left ObjCClass, right ObjCClass) -> Bool {
    return left.isEqualTo(right)
}

But we have to do this for every Objective-C class we want to define as Drawable.

The alternative is to define our protocol as you presented it, using isEqualTo:, a very commonplace Objective-C method.

Now, to make all of our Swift types conform to Drawable, all we have to do is implement the draw() method and conform to Equatable. As long as we conform to Equatable, the extension will add the isEqualTo: method to our Swift type as a simple return left == right effectively (and the existence of the == method is what the Equatable protocol guarantees).

nhgrif
  • 58,130
  • 23
  • 123
  • 163
  • Great answer! Thanks so much! For your roughly equivalent alternate solution, the lecturer mentioned it but said something along the lines of Equatable requires self and we were working with heterogeneous arrays (I don't understand why this matters) – rb612 Jun 25 '15 at 00:33
  • That doesn't make sense to me out of context... but I'm glad my answer helped you understand. – nhgrif Jun 25 '15 at 00:35
  • You can find the part of the lecture [here](https://youtu.be/g2LwFZatfTI?t=38m4s), and at 39 minutes exactly he mentions why your solution wouldn't work and being kind of a beginner to programming with Swift, his explanation for why it doesn't work and the alternate solution makes no sense to me. – rb612 Jun 25 '15 at 00:44