Type bound in Scala function complicates piping to method reference

元气小坏坏 提交于 2020-06-12 08:46:11

问题


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 to T 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 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 }
}

回答1:


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(_).




回答2:


I think this is a variation of return-current-type problem. Consider typeclass solution

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)


来源:https://stackoverflow.com/questions/61782582/type-bound-in-scala-function-complicates-piping-to-method-reference

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!