1

I'd like to have an interface and implementing class/object similar to the following:

interface EventBus {
    suspend fun <T : Message> publish(message: T)
    suspend fun <R : Command, S : CommandResponse> request(command: R): Either<EventbusError, S>
    suspend fun close(): Either<EventbusError, Unit>
    //? fun <T : Message> subscribe(): Flow<T>
}

object EventBusImpl : EventBus {
    private val _eventBus = MutableSharedFlow<Message>()
    val messages = _eventBus.asSharedFlow()

    override suspend fun <T : Message> publish(message: T) {}
    override suspend fun <R : Command, S : CommandResponse> request(command: R): Either<EventbusError, S> {}
    override suspend fun close(): Either<EventbusError, Unit> {}
    inline fun <reified T:Message> subscribe():Flow<T> = messages.filterIsInstance<T>()
}

I understand that inline functions cannot be over overridden and thus cannot be part of an interface, but as the subscribe() function is an important part of the API, I'd still like to represent it somehow in the interface, without falling back to passing a Class<T> as an argument.

How could this be accomplished?

This

interface EventBus {
    suspend fun <T : Message> publish(message: T)
    suspend fun <R : Command, S : CommandResponse> request(command: R): Either<EventbusError, S>
    suspend fun close(): Either<EventbusError, Unit>
    suspend fun <T : Message> subscribe(type: Class<T>): Flow<T>
}

of course works, but is not very Kotlin'ish

macnixde
  • 213
  • 1
  • 9
  • I suppose you do not want to make `messages` part of the interface? – Sweeper Nov 21 '22 at 11:10
  • `val messages:SharedFlow` would then need to become a member of the interface - but only if the inlined function would be in the interface. Otherwise it would remain an 'implementation detail'. – macnixde Nov 21 '22 at 11:37
  • That doesn't really answer my question - do you *mind* `messages` being part of the interface, and not an implementation detail? Because if you don't mind, you can make `subscribe` an inline extension function on the interface instead. – Sweeper Nov 21 '22 at 11:40
  • well - I think I answered your question: I don't mind it being public if *necessary*. An extension function on the interface would not be part of the interface and thus defeat the purpose of an interface IMO. This is effectively the same as implementing the inline function in the implementation class/object but not in the interface. – macnixde Nov 21 '22 at 11:42
  • I see, so your goal is to make `subscribe` have multiple different implementations, and being able to dynamically dispatch to those implementations, right? I don't think that's possible, sorry. – Sweeper Nov 21 '22 at 11:47

1 Answers1

1

The comments on your question are correct: It is not possible to have a virtual inlined member as the implementation must be known at compile-time in order to inline it.

However, you can work around it by leaving this virtual member in your interface:

suspend fun <T : Message> subscribe(type: Class<T>): Flow<T>

and adding this inline extension function:

suspend inline fun <reified T : Message> EventBus.subscribe() = subscribe(T::class)

Now you can use subscribe as you intended:

suspend fun foo(bus: EventBus) {
    val flow = bus.subscribe<MyMessage>()
}

This has the advantage of keeping subscribe a virtual member in the original interface (so it must be implemented by subtypes) and better calling syntax via the inline extension function.

Alex
  • 634
  • 10
  • 29