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:
PreEvent its copy has to be explicitly cast to T for the compiler to realise it is the same type as the method parameter.PreEvent to a method reference the compiler suddenly forgets it is dealing with a PreEvent and attempts to call the Event variant of publish.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 }
}
Regarding your first question, see details here
Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?
Type mismatch on abstract type used in pattern matching
The thing is that def reduce[T <: PreEvent](event: T): T is just incorrect signature for
event match {
case it: PreConnectEvent => it.copy(cancelled = true)
case _ => event
}
Correct one would be def reduce[T is a subclass of PreEvent](event: T): T if such syntax were possible in Scala (<: means "is a subtype of").
Please consider type class approach (see @MarioGalic's answer) or type tags approach (see above link).
Regarding your second question, you can write
val reduced = PreConnectEvent(UUID.randomUUID()).pipe(eventService.publish[PreConnectEvent])
specifying that you're using the overloaded version of method that is generic. Well, hardly it's shorter than eventService.publish(_).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With