为什么scala编译器不会从超类中推断出类型参数?

时间:2016-09-22 22:29:59

标签: scala

我想理解为什么scala编译器无法推断传递给超类的类型参数,以便我可以提出一种解决方法。解决方案建议也非常欢迎!这是我所坚持的一个人为的例子(代码中的注释解释问题):

代码也在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

3 个答案:

答案 0 :(得分:4)

问题在于Scala尝试同时推断ReqImplResp类型参数,而不是首先推断ReqImpl并从中获取Resp。由于Resp实际上并未出现在参数列表中,因此它被推断为Nothing,然后违反了Scala通知类型边界。解决方法(我不记得我先看到它的位置)是给req一个等效的类型,但明确依赖Resp的那个:

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"))现在编译。

答案 1 :(得分:0)

我认为您需要捕获Req的Type参数与您的Svc特征中apply函数的类型参数之间的关系。然后你可以相应地修改其余部分。

trait Svc[Req[_ <: XX], XX] {
  def apply[Resp <: XX](req: Req[Resp]): Resp
}

答案 2 :(得分:0)

这样做的一种方法是明确提及您的Convert.ToDouble是参数化类型(Type infered to Nothing in Scala)。在您的情况下,它将如下所示:

ReqImpl

但这种方法存在两个问题:

(1)在def marshal[ReqImpl[Resp] <: MyReq[Resp], Resp](req: ReqImpl[Resp])( implicit marshaller: ReqMarshaller[ReqImpl[Resp], Resp] ): String = marshaller.marshal(apply(req)) 中,Scala会将svc.marshal(GetInt("me"))的类型推断为RepImpl,哪种有意义,但MyReq[Int]无法匹配。所以你需要将它定义为:

ReqMarshaller[GetInt, Int]

(2)现在您遇到了另一个问题,您无法同时定义两个implicit val getIntMarshaller = new ReqMarshaller[MyReq[Int], Int] { def marshal(i: Int): String = (i * i).toString } 。也许用相同的类型参数定义两个端点是个坏主意(只是一个猜测,但有些东西不适合这里,它也不能与Alexey Romanov的解决方案一起工作)

<强>更新

(1)通过使ReqMarshaller[MyReq[String], String]协变来解决:

ReqMarshaller

(2)仍然失败,含糊不清。