Scala函数中的类型绑定将管道整理为方法引用

问题描述 投票:2回答:1

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

Event交互按预期方式工作,但是PreEventT <: PreEvent类型界限给了我一些问题:

  • ((1)匹配PreEvent时,其副本必须显式转换为T,以使编译器意识到它的类型与方法参数相同。
  • ((2)尝试将PreEvent传递给方法引用时,编译器突然忘记了它正在处理PreEvent,并尝试调用Eventpublish变体。

除了重命名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 }
}
scala generics
1个回答
0
投票

我认为这是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)
© www.soinside.com 2019 - 2024. All rights reserved.