Mark S Mark S - 1 month ago 4
Scala Question

Why doesn't scala compiler infer type parameter from superclass?

I want to understand why the scala compiler cannot infer a type parameter passed to a superclass so that I can come up with a workaround. Workaround suggestions are also very welcome! Here's a contrived example of what I'm stuck on (comments in the code explaining issues):

Code is also in a scala fiddle.

/** A Svc is a function that responds to requests
* @tparam Req[_] a request ADT whose instances specify their response type
trait Svc[Req[_]] {
def apply[Resp](req: Req[Resp]): Resp

/** Service request ADT */
sealed trait MyReq[_]
// two requests have the same response type of String (i.e. MyReq[String]):
case class GetString(id: String) extends MyReq[String]
case class GetAltString(id: String) extends MyReq[String]
// this one is the only MyReq[Int]
case class GetInt(id: String) extends MyReq[Int]

/** Type class for marshalling a response for a concrete request type.
* This lets us handle marshalling differently for different requests
* that have the same response type (such as GetString and GetAltString above).
* @tparam ReqImpl concrete MyReq type. This is required to enforce unique marshaller
* per request when there are mutliple request types with the same response type.
trait ReqMarshaller[ReqImpl <: MyReq[Resp], Resp] {
def marshal(r: Resp): String

class MySvc extends Svc[MyReq] {
// this apply function compiles and works just fine.
override def apply[Resp](req: MyReq[Resp]): Resp = req match {
case GetString(id) => id
case GetAltString(id) => id + id
case GetInt(id) => id.length

// This is the problem. I want to specify the request is a subclass so
// we get the specific marshaller for the request type and avoid
// ambiguous implicit errors.
// However, the Resp type parameter is always inferred as Nothing
// instead of the correct response type.
def marshal[ReqImpl <: MyReq[Resp], Resp](req: ReqImpl)(
marshaller: ReqMarshaller[ReqImpl, Resp]
): String = marshaller.marshal(apply(req))

// this method is just here to show that it won't work as a solution
// because it doesn't work when there are multiple request types with
// the same response type (causes ambiguous implicits errors)
def marshalGeneric[Resp](req: MyReq[Resp])(
marshaller: ReqMarshaller[_ <: MyReq[Resp], Resp]
): String = marshaller.marshal(apply(req))

implicit val getIntMarshaller: ReqMarshaller[GetInt, Int] = new ReqMarshaller[GetInt, Int] {
def marshal(i: Int): String = (i * i).toString

implicit val getStrMarshaller: ReqMarshaller[GetString, String] = new ReqMarshaller[GetString, String] {
def marshal(s: String): String = s

implicit val getAltStrMarshaller: ReqMarshaller[GetAltString, String] = new ReqMarshaller[GetAltString, String] {
def marshal(s: String): String = s + s

val svc = new MySvc

val myLength = svc(GetInt("me")) // 2
println(s"myLength: $myLength")

svc.marshalGeneric(GetInt("me")) // compiles and works
//svc.marshal(GetInt("me")) // fails to compile due to infering Resp type as Nothing
//svc.marshalGeneric(GetAltString("me")) // fails to compile because of ambiguous implicits


The problem is that Scala is trying to infer both ReqImpl and Resp type parameters at once instead of inferring ReqImpl first and getting Resp from that. Since Resp doesn't actually appear in the parameter list, it's inferred to Nothing and then Scala notices type bounds are violated. A workaround (I don't remember where I saw it first) is to give an equivalent type to req but one which depends on Resp explicitly:

def marshal[ReqImpl <: MyReq[Resp], Resp](req: ReqImpl with MyReq[Resp])(
  implicit marshaller: ReqMarshaller[ReqImpl, Resp]
): String = marshaller.marshal(apply(req))

svc.marshal(GetInt("me")) now compiles.