Conform to generic protocol twice

I have the following generic protocol:

protocol EventListener<Event> {
    associatedtype Event

    func onEvent(_ event: Event)
}

Some classes can receive different events, so they should conform to EventListener multiple times, each with a different Event type. Something like this:

class ConcreteListener: EventListener<EventA>, EventListener<EventB> {
    func onEvent(_ event: EventA) { /* ... */ }
    func onEvent(_ event: EventB) { /* ... */ }
}

But this isn’t supported in Swift since you can’t conform to a protocol twice, and the P<T> syntax doesn’t work with protocols.


Is there a workaround or a better way to handle this in Swift today?

1 Like

Maybe you could write something like this:

enum Either<A, B> {
    case first(A)
    case second(B)
}

class ConcreteListener: EventListener {
    typealias Event = Either<EventA, EventB>

    func onEvent(_ event: Either<EventA, EventB>) {
        switch event {
        case let .first(a):
            onEvent(a)
        case let .second(b):
            onEvent(b)
        }
    }

    func onEvent(_ event: EventA) {}
    func onEvent(_ event: EventB) {}
}
1 Like

But then you'd need to change the event emitters as well to emit the new enum not the events they were emitting before, which doesn't make sense as each emitter produces a single type of event not any one of them.

I don’t know your exact use case. If you can share a bit more sample code or context about how you emit and consume these events, I might be able to give you a more suitable solution.

1 Like

Swift does not have generic protocols. What you have is a protocol with a primary associated type. The evolution proposal for primary associated types acknowledges the difference, but Slava provides a true exploration of the problems that a generic protocols would introduce.

3 Likes

I do not know what your use cases are, but you can do things without protocols:

struct Handler<Event> {
    let handler: (Event) -> Void

    func handle(_ event: Event) {
        handler(event)
    }
}

class A {
    func handle(_ value: Int) {

    }
    func handle(_ value: Double) {

    }

    // can be synthesized by macro system
    var asIntHandler: Handler<Int> {
        .init(handler: self.handle)
    }

    var asDoubleHandler: Handler<Double> {
        .init(handler: self.handle)
    }
}

it can be used this way:

// invoke directly
A().handle(3)
// invoke abstractly
let doubleHandlers: [Handler<Double>] = [
    A().asDoubleHandler,
    A().asDoubleHandler
]
doubleHandlers.forEach {
    $0.handle(1024.0)
}

You can reduce boilerplate by implementing a macro, for example:

@attached()
macro Handling<each Param>(types: repeat (each Param).Type) = /*...*/

@Handing(Int.self, Double.self)
class A {
    /*...*/
    // automatically create a peer member for each `func handle()`
}
2 Likes

I like your example, but how would I do the following?

// invoke abstractly
let handlers: [Handler<Int> or Handler<Double>] = [
    A().asIntHandler,
    A().asDoubleHandler
]

Maybe parameter packs?

func handle<each Event>(
  handler: repeat Handler<each Event>,
  value: repeat each Event
) {
  repeat (each handler).handle(each value)
}

handle(
  handler: A().asIntHandler, A().asDoubleHandler,
  value: 1, 1.0
)

I suspect the general solution to this problem requires type erasure, with generic accessors that assert dynamic type identity at runtime.

It can be implemented using double dispatch. The following code works with @CrystDragon's class A (note A is already a vistor in vistor pattern). I'll omit visitor's protocol for simplicity and use A directly.

protocol Event {
    func accept(_ handler: A)
}

struct IntEvent: Event {
    let value: Int = 0
    func accept(_ handler: A) { handler.handle(value) }
}

struct DoubleEvent: Event {
    let value: Double = 0.0
    func accept(_ handler: A) { handler.handle(value) }
}

let events: [any Event] = [IntEvent(), DoubleEvent()]
let handler = A()
for e in events {
    e.accept(handler)
}
1 Like