I've implemented a simple event service that allows for the publishing of read-only events (Event
), and cancellable events (PreEvent
). Cancellable events are reduced by all handlers and the result is returned to the caller.
The Event
interaction works as expected, but the PreEvent
type bound of T <: PreEvent
is giving me some issues:
- (1) When matching a
PreEvent
its copy has to be explicitly cast toT
for the compiler to realise it is the same type as the method parameter. - (2) When attempting to pipe a
PreEvent
to a method reference the compiler suddenly forgets it is dealing with aPreEvent
and attempts to call theEvent
variant ofpublish
.
Aside from renaming the EventService::publish(PreEvent)
method to avoid the disambiguation, are there any changes I could make to the type bound of Handler::reduce[T <: PreEvent](event: T): T
to help Scala realise (event: T)
will always be a PreEvent
when the method is passed as a method reference? (and thus has no type information available, eventhough I would have expected the compiler to figure this out from the context)
Are there any changes I could make to the type bound of Handler::reduce[T <: PreEvent](event: T): T
or handler match statement to help Scala realise that when I'm matching on the event parameter and copying that explicit event it will by default be of the same type as the parameter and thus the type bound?
import java.util.UUID
import scala.util.chaining._
trait Event
trait PreEvent
trait Handler {
def handle(event: Event): Unit = {}
def reduce[T <: PreEvent](event: T): T = event
}
class EventService {
private var handlers: List[Handler] = Nil
def publish(event: Event): Unit =
handlers.foreach { _.handle(event) }
def publish[T <: PreEvent](event: T): T =
handlers.foldLeft(event) { (event, handler) => handler.reduce(event) }
}
// this works fine
case class ConnectEvent(id: UUID) extends Event
class ConnectHandler extends Handler {
override def handle(event: Event): Unit = event match {
case ConnectEvent(id) =>
case _ =>
}
}
// problem 1
case class PreConnectEvent(id: UUID, cancelled: Boolean = false) extends PreEvent
class PreConnectHandler extends Handler {
override def reduce[T <: PreEvent](event: T): T = event match {
// (1) the copy result needs to be explicitly cast to an instance of T
case it: PreConnectEvent => it.copy(cancelled = true).asInstanceOf[T]
case _ => event
}
}
// problem 2
class Service(eventService: EventService) {
def publishPreEvent(): Unit = {
// (2) the method reference of 'eventService.publish' needs to be explicitly turned
// into an anonymous function with '(_)' or it tries to call EventService::publish(Event)
val reduced = PreConnectEvent(UUID.randomUUID()).pipe { eventService.publish(_) }
// do something with reduced event
}
// this works fine
def publishEvent(): Unit =
ConnectEvent(UUID.randomUUID()).tap { eventService.publish }
}