Extensions for existential types. Custom existential containers

Consider, this example:

protocol P {
    var asHashable: AnyHashable { get }
}

extension P where Self: Hashable {
	var asHashable: AnyHashable {
		return AnyHashable(self)
	}
}

struct A: P, Hashable {}
struct B: P, Hashable {}

let item1: P = A()
let item2: P = B()
let itemsEqual = item1.asHashable == item2.asHashable
let array1: [P] = [A(), B()]
let array2: [P] = [A(), B()]
let arraysEqual = array1 == array2

I want to work with items conforming to type P, but potentially of different types, so I cannot use generics, but existential containers save the day.

But also, I want to be able to compare such items, including items of different types. I cannot conform my root protocol to Hashable, because that would break packing into existential container. After a bit of thinking I was able to solve this with a workaround above and now I can compare my items using asHashable property.

That works acceptably when comparing individual items, but does not scale. Normally when you can compare type T (it conforms to Equatable/Hashable) you also get comparison of [T] and T? for free.

But for my workaround that's not the case.

To enable compiler to synthesize conformance of [P] and P? to Equatable, I need to tell compiler that element type conforms to Equatable.

Despite of syntax, element type is actually not a protocol. It is an existential container for protocol. [P] stands for "array of existential containers for P".

I could trivially implement conformance of existential containers to Equatable, but currently compiler does not allow me to express this. extension P would mean protocol extension. There is no way in the language to express that I want to have an extension for existential container, rather than protocol.

Current approach it to have a custom struct that serves as a hand-written existential container and explicitly conforms to Equatable. But values need to be wrapped into existential container manually, which creates a lot of boilerplate code.

My problem would be solved if I could either:

  1. declarare extensions for existential container, something like:
    extension *P {}
    or
    extension Existential where Value: P {}
  2. enable custom struct to be used a custom existential container allowing implicit conversion from values conforming to P
2 Likes