user1007522
user1007522

Reputation: 8118

Force objects to implement Comparable with protocol gives protocol can only be used as a generic constraint

I'm having an array of objects. Now I want to force that those objects are implementing Comparable.

So I created a protocol like this:

protocol Foo: Comparable { }

Everything goes fine except when I create the array:

let array: [Foo] = [obj1, ..]

Then suddenly the compiler complains:

Protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements

Is there another way to do this?

EDIT

class View: UIView {
    let viewModel: SomeViewModel
}

class SomeViewModel {
     let views: [UIView]
}

Upvotes: 2

Views: 46

Answers (1)

Rob Napier
Rob Napier

Reputation: 299345

Generally when people add this conformance, they're trying to do something like sort a [Foo] or find the minimum value, but that won't work, even if you could make this Array, because Comparable requires that a type be comparable to its own type, not to others matching the protocol. So for, example, given your array, you couldn't use array.min() to find the minimum element, because Comparable does not require that a Foo be comparable to a Foo, only that things that conform to Foo can be compared to themselves.

Instead, you need to extend the protocol to require that conforming types are able to compare themselves against any Foo:

protocol Foo: Comparable { 
    // ... existing requirements

    func isEqual(to: Foo) -> Bool
    func isLessThan(_: Foo) -> Bool
}

Given those, you can construct whatever other algorithms you need. You can't use the default forms of contains or sorted, but you can use these functions in the where and by parameters.

You can also make this a little nicer by providing some default implementations:

extension Foo where Self: Equatable {
    func isEqual(to other: Foo) -> Bool {
        guard let other = other as? Self else {
            return false
        }
        return self == other
    }
}

And similarly for isLessThan.

And if you want things like contains, you can then also provide helpers if that's convenient:

extension Sequence where Element == Foo {
    func contains(_ element: Element) -> Bool {
        return contains(where: { $0.isEqual(to: element) })
    }
}

If you want a much longer version of this discussion, see Generic Swift: It Isn't Supposed to Hurt. This particular subject comes up at 37:40.

Upvotes: 1

Related Questions