首页 > 解决方案 > Scala 函数中的类型绑定使方法引用的管道复杂化

问题描述

我已经实现了一个简单的事件服务,它允许发布只读事件 ( Event) 和可取消事件 ( PreEvent)。所有处理程序都会减少可取消的事件,并将结果返回给调用者。

交互按预期工作,EventPreEvent类型限制T <: PreEvent给了我一些问题:

除了重命名EventService::publish(PreEvent)方法以避免歧义之外,我是否可以对类型绑定进行任何更改Handler::reduce[T <: PreEvent](event: T): T以帮助 Scala 实现在方法作为方法引用传递时(event: T)始终是 a ?PreEvent(因此没有可用的类型信息,尽管我希望编译器从上下文中找出这一点)

是否可以对类型绑定Handler::reduce[T <: PreEvent](event: T): T或处理程序匹配语句进行任何更改,以帮助 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 }
}

标签: scalagenerics

解决方案


我认为这是返回电流类型问题的一种变体。考虑类型类解决方案

trait Handler {
  def handle(event: Event): Unit = {}
  def reduce[T](event: T)(implicit ev: EventReducer[T]): T = ev.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)

推荐阅读