Can you create a Set of a single protocol?

I'm wondering, is it even possible to create a `Set` in Swift whose members are guaranteed only to share conformance to a Protocol?


My initial attempt looked like this:

protocol MyProtocol {}
var mySet: Set<MyProtocol> = []

But that yields the compiler error "Type 'MyProtocol' does not conform to protocol 'Hashable'".


Next I tried:

protocol MyProtocol: Hashable { }
var mySet: Set<MyProtocol> = []

But now we get the error "Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements"

Clearly we've hit a problem with the Swift distinction between static and dynamic protocols that prevents us from using a Hashable protocol as a type parameter (because Hashable is Equatable, and Equatable for some reason requires that you're trying to equate a class to itself).


What if we try adding Hashable compliance to MyProtocol via an extension?

protocol MyProtocol {}
extension MyProtocol: Hashable {}

"Extension of protocol 'MyProtocol' cannot have an inheritance clause" - Ok, so protocol extensions cannot add protocol conformance like class extensions can.


Ok, how about we try to add conformance via a protocol extension to Hashable?

protocol MyProtocol {}
extension Hashable where Self: MyProtocol {
    var hashValue: Int { get { return 0 } } // TODO: An actual implementation
}
var mySet: Set<MyProtocol> = []

"Type 'MyProtocol' does not conform to protocol 'Hashable'" - Ok, we can't add protocol conformance like that

Protocol composition as part of the Set type parameter?

protocol MyProtocol {}
var mySet: Set<protocol<MyProtocol, Hashable>> = []

"Protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements" - here we are again


The one thing that does seem to work is:

protocol MyProtocol, Hashable {}
func getMySet<T: MyProtocol> -> Set<T> {
    return []
}

But now we have the problem that when we create our Set we need to provide a class (because, again, we can't use a protocol that extends MyProtocol as our T parameter because you can't use any protocol that inherits from MyProtocol as a type parameter) that all the members of our Set are guaranteed to extend.


This is severely limiting to the power of Protocols because now I cannot, for example, construct a set of all UserLike objects without knowing a single class (assuming there even is one) that all UserLike objects will also subclass. Is there a way to accomplish this without requiring that all members of my Set extend a given class?

So, assuming that it is not possible (without resorting to creating a custom Set type etc), one wonders if there is any particular reason for why Hashable should/must imply Equatable, or rather why the == of Equatable should/must have Self parameters.


Btw, here's another (boilerplaty) workaround, using an enum:

protocol SomeProtocol : CustomStringConvertible {
}

struct Foo : SomeProtocol {
    var description: String { return "Foo\(id)"  }
    let id: Int
    init(_ id: Int) { self.id = id }
}

struct Bar : SomeProtocol {
    var description: String { return "Bar\(id)"  }
    let id: Int
    init(_ id: Int) { self.id = id }
}

struct Baz : SomeProtocol {
    var description: String { return "Baz\(id)"  }
    let id: Int
    init(_ id: Int) { self.id = id }
}

enum ConformingToSomeProtocol : Hashable, CustomStringConvertible {
    var description: String {
        switch self {
        case .FooValue(let v): return v.description
        case .BarValue(let v): return v.description
        case .BazValue(let v): return v.description
        } // Any shorter way to do this?
    }
    case FooValue(Foo)
    case BarValue(Bar)
    case BazValue(Baz)
    var hashValue: Int {
        switch self {
        case .FooValue(let v): return v.description.hashValue
        case .BarValue(let v): return v.description.hashValue
        case .BazValue(let v): return v.description.hashValue
        } // Any shorter way to do this?
    }
}

func ==(lhs: ConformingToSomeProtocol, rhs: ConformingToSomeProtocol) -> Bool {
    switch (lhs, rhs) {
    case (.FooValue(let l), .FooValue(let r)): return l.description == r.description
    case (.BarValue(let l), .BarValue(let r)): return l.description == r.description
    case (.BazValue(let l), .BazValue(let r)): return l.description == r.description
    default: return false
    } // Any shorter way to do this?
}

let set: Set<ConformingToSomeProtocol> = [
    .FooValue(Foo(1)),
    .FooValue(Foo(2)),
    .FooValue(Foo(2)),
    .BarValue(Bar(1)),
    .BarValue(Bar(2)),
    .BarValue(Bar(3)),
    .BazValue(Baz(1)),
    .BazValue(Baz(1))
]

print(set) // [Bar2, Bar1, Bar3, Foo1, Foo2, Baz1]


As can be seen, it uses an enum to wrap the value of any type that conforms to SomeProtocol, and such enums are put in the set. This will also let you get the value out of the set with its proper type, or you can forward any required properties or functions through the enum.


In this example, I happened to use the description property of CustomStringConvertible (former Printable) as the (only) common/required thing for types conforming to SomeProtocol, but of course it could be anything else, as long as something of it can be used for the hashing and ==.

Hashable inherits from Equatable because hash values are typically used as a shortcut to speed up identifying potentially equal items.


It's functionality added on to Equatable to make equality checking faster (by limiting the pool of items that need the full equality comparison). But since the hash value is typically smaller than the object it was generated from, there are conflicts and equality can't be determined from equal hash values alone.


If Hashable didn't inherit from Equatable, pretty much everything that used a Hashable type would need to be updated to require Hashable & Equatable anyway.



Equatable is defined in terms of Self, because otherwise there wouldn't be any way to identify/enforce the parameter types for the required function.

Though everything you wrote makes sense, I am not sure I totally agree with the last sentence. I feel like, since equality is perfectly well defined in the case where you are comparing two objects of different class heirarchies (i.e. it should return false), there should be a way to rewrite Equatable to not require Self reference, especially since having Self reference required severely limits the abilities of the Set class and sorting functions.


I guess for that matter, I also don't fully understand why Self referencing protocols can only be used as Type Constraints and not as Types themselves.

“I guess for that matter, I also don't fully understand why Self referencing protocols can only be used as Type Constraints and not as Types themselves.”


To prevent exactly the kind of thing you're trying to do here. If you could make a heterogenous set of things that conform to a protocol, then equality wouldn't be defined between elements in the set, and the data structure couldn't function. The same argument applies to trying to test equality between two variable of type Equatable.

But I just made such a set (see code above), and decided I would let their equality/identity be defined via their description property.

No, not really. You made a homogeneous set of an enum.

Haha, I guess the answer of "why can't I do this" being "because it's something that is literally EXPLICITLY NOT ALLOWED" is a fair one. 😁


I understand why members of Set need to be equality tested against each other, though I am still having trouble wrapping my head around the part where Equatable only allows comparisons between items that the compiler identifies as part of the same class hierarchy. Mostly, I suppose that my difficulty with that understanding comes from all the other languages (obj-c, java, scala, python, javascript, etc.) where equality comparison between unrelated classes is a totally valid thing and feel like the convenience that comes from *not* having to write a one-off wrapper class to implement Equatable and Hashable every time I want to create a Set of objects whose membership is determined by protocol coherence would be pretty nice.

To expand on that last point, one could imagine a reworking of Equatable that would take after the way those other languages have defined it:

protocol Equatable {
 func isEqualTo(item: Equatable) -> Bool
}

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


Which would allow us to still use the == operator to test equality between two Equatable objects, but also make Equatable no-longer a self-referencing protocol.

Yeah, there might possibly be a way to rework Equatable to not have Self requirements, though there are probably consequences to that as well. I was only trying to address the question of why protocols with Self requirements can only be used as generic constraints.

The current definition of the Equatable protocol simplifies equality testing by guaranteeing that the two objects share a specific type.


Your revised version that requires two independent Equatable objects as parameters to == doesn't provide that guarantee, so every implementation of .isEqualTo() would need to perform some kind of type checking. There's really no reason to have .isEqualTo() require an Equatable parameter, so it might as well just take Any.


And when you do that, it's actually very difficult to determine how to compare two arbitrary objects... how do you determine whether or not there is a way to compare them, other than pointer identity (===)? How do you tell the compiler/runtime which other method/function to use that compares those two objects more accurately (particularly value types, which may be equal but different copies)?

It is fairly easy to determine how to compare two arbitrary objects. As mentioned, plenty of other languages already do this. In a simple version, any two objects which are not the same class could just return false and if they were the same class could do something like the current Equatable method. Similarly Objective-C allows you to compare any objects with isEqual: and you can customize the implementation to allow even different classes to return that they are equatable if this is a thing that makes sense for your use case. Zack's latest example above is basically this; implementing Equatable in the way similar to how Objective-C implements it (with the added benefit of being able to use the == operator).

In order to make it less boilerplate-ish and to avoid having to update the wrapper to allow new classes to adopt the protocol, an struct wrapper is probably a simpler solution than an enum.


protocol SomeProtocol
{
    /* ... */

    var hashValue: Int {get}
    func isEqualTo(v: SomeProtocol) -> Bool
}

struct SomeProtocolThing: Hashable
{
    let thing: SomeProtocol

    var hashValue: Int {return thing.hashValue}
}
func ==(lhs: SomeProtocolThing, rhs: SomeProtocolThing) -> Bool {return lhs.thing.isEqualTo(rhs.thing)}


var things = Set<SomeProtocolThing>()


or, if any difference in run-time type (even subclasses) is enough to make two things unequal

protocol SomeProtocol
{
    /* ... */

    var identifier: String {get}
}


struct SomeProtocolThing: Hashable
{
    let thing: SomeProtocol

    var hashValue: Int {return thing.identifier.hashValue}
}
func ==(lhs: SomeProtocolThing, rhs: SomeProtocolThing) -> Bool
{
    guard ("\(lhs.thing.dynamicType)" == "\(rhs.thing.dynamicType)") else {return false} // false if runtime type is different
    return lhs.thing.identifier == rhs.thing.identifier
}


var things = Set<SomeProtocolThing>()

(and you can take out the dodgy-looking guard statement in == if the name of the type is included in the identifier string so that identifier strings from instances of two different classes won't be equal)

Have you actually tried to write Swift code that does this correctly? It's much harder than you might think.

For example, there is a subtle logic trap/error in Zack's implementation of ==

protocol Equatable {
    func isEqualTo(item: Equatable) -> Bool
}
func ==(lhs: Equatable, rhs: Equatable) -> Bool {
    return lhs.isEqualTo(rhs)
}


It should probably read

protocol Equatable {
    func isEqualTo(item: Equatable) -> Bool
}

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

Yeah, that's a lot better than my enum workaround.

Can you create a Set of a single protocol?
 
 
Q