Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type bound in Scala function complicates piping to method reference

Tags:

generics

scala

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 }
}
like image 814
fishb6nes Avatar asked Mar 10 '26 04:03

fishb6nes


1 Answers

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

like image 161
Dmytro Mitin Avatar answered Mar 11 '26 18:03

Dmytro Mitin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!