我已经实现了一个简单的事件服务,该服务允许发布只读事件(Event
)和可取消事件(PreEvent
)。所有处理程序都会减少可取消的事件,并将结果返回给调用方。
Event
交互按预期方式工作,但是PreEvent
的T <: PreEvent
类型界限给了我一些问题:
PreEvent
时,其副本必须显式转换为T
,以使编译器意识到它的类型与方法参数相同。PreEvent
传递给方法引用时,编译器突然忘记了它正在处理PreEvent
,并尝试调用Event
的publish
变体。除了重命名EventService::publish(PreEvent)
方法以避免歧义,我还可以对Handler::reduce[T <: PreEvent](event: T): T
的类型范围进行任何更改,以帮助Scala在按以下方式传递方法时实现(event: T)
始终为PreEvent
:方法参考? (因此没有可用的类型信息,尽管我希望编译器从上下文中解决这个问题)
我可以对Handler::reduce[T <: PreEvent](event: T): T
的类型限制或处理程序match语句进行任何更改,以帮助Scala意识到,当我匹配事件参数并复制该显式事件时,默认情况下,该类型将与参数和类型绑定?
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 }
}
我认为这是return-current-type problem的变体。考虑typeclass解决方案
trait Handler {
def handle(event: Event): Unit = {}
def reduce[T : EventReducer](event: T): T = implicitly[EventReducer[T]].reduce(event)
}
trait EventReducer[T] {
def reduce(event: T): T
}
object EventReducer {
implicit val preConneectEventReducer: EventReducer[PreConnectEvent] =
(it: PreConnectEvent) => it.copy(cancelled = true)
implicit def otherEventReducer[T]: EventReducer[T] =
(event: T) => event
}
(new PreConnectHandler).reduce(PreConnectEvent(UUID.randomUUID()))
// res0: PreConnectEvent = PreConnectEvent(99bcd870-4b7d-4b28-a12a-eafe4da16c78,true)
(new PreConnectHandler).reduce(ConnectEvent(UUID.randomUUID()))
// res1: ConnectEvent = ConnectEvent(47af28b7-ea93-4da1-9ee6-e89d41540141)