Given the following classes:
case class AddRequest(x: Int, y: Int)
case class AddResponse(sum: Int)
case class ToUppercaseRequest(str: String)
case class ToUpper
You can define a common trait for Requests, and a common trait for Responses where the request type is defined for specific response type:
trait Request[R <: Response]
trait Response
case class AddRequest(x: Int, y: Int) extends Request[AddResponse]
case class AddResponse(sum: Int) extends Response
case class ToUppercaseRequest(str: String) extends Request[ToUppercaseResponse]
case class ToUppercaseResponse(upper: String) extends Response Response[ToUppercaseRequest]
Then, process
signature would be:
def process[A <: Request[B], B <: Response](req: A): B
When you call process
, you'll have to explicitly define the types so that the returned type is what you expect it to be - it can't be inferred specifically enough:
val r1: AddResponse = process[AddRequest, AddResponse](AddRequest(2, 3))
val r2: ToUppercaseResponse = process[ToUppercaseRequest, ToUppercaseResponse](ToUppercaseRequest("aaa"))