0

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)(
    implicit
    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])(
    implicit
    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
Mark S
  • 1,488
  • 12
  • 19

3 Answers3

4

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.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • This solves the compiler inferring the `Resp` type to be `Nothing` - but I'm still getting compiler error for ambiguous implicits for `svc.marshalGeneric(GetAltString("me"))`. But, you have answered my question. Thank you! – Mark S Sep 23 '16 at 19:42
  • For `marshalGeneric(GetAltString("me"))` you simply do have ambiguous implicits: both `getStrMarshaller` and `getAltStrMarshaller` are suitable. Do you want it to do something different from the fixed `marshal`? – Alexey Romanov Sep 23 '16 at 20:38
  • Of course you are right. I forgot to change the `marshalGeneric(GetAltString("me"))` back to `marshal(GetAltString("me"))`. Thanks again! – Mark S Sep 23 '16 at 20:48
0

I think you will need to capture the relationship between Type parameter of Req and type param of your apply function in your Svc trait. And then you can modify the rest of things accordingly.

trait Svc[Req[_ <: XX], XX] {
  def apply[Resp <: XX](req: Req[Resp]): Resp
}
sarveshseri
  • 13,738
  • 28
  • 47
0

One way of doing this is to explicitly mention your ReqImpl is a parametrised type (Type infered to Nothing in Scala). In your case it will look like this:

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

But there are two problems with this approach:

(1) In svc.marshal(GetInt("me")) Scala will infer the type of RepImpl as MyReq[Int], which kind of make sense, but ReqMarshaller[GetInt, Int] won't match. So you need to define it as:

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

(2) Now you immediately have another problem, you can't define two ReqMarshaller[MyReq[String], String] at the same time. And maybe it's a bad idea to define two endpoints with the same type parameter (just a guess, but something doesn't fit here, it doesn't work with Alexey Romanov's solution either).

UPDATE

(1) is solved by making ReqMarshaller covariant:

trait ReqMarshaller[+ReqImpl <: MyReq[Resp], Resp] { ...

(2) still fails with ambiguous implicits.

Community
  • 1
  • 1
Victor Moroz
  • 9,167
  • 1
  • 19
  • 23