【问题标题】:Why doesn't scala compiler infer type parameter from superclass?为什么scala编译器不从超类推断类型参数?
【发布时间】:2016-09-22 22:29:59
【问题描述】:

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

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

【问题讨论】:

标签: scala


【解决方案1】:

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

【讨论】:

  • 这解决了编译器将Resp 类型推断为Nothing - 但我仍然收到编译器错误,因为svc.marshalGeneric(GetAltString("me")) 的隐含含糊不清。但是,你已经回答了我的问题。谢谢!
  • 对于marshalGeneric(GetAltString("me")),您确实有模棱两可的隐含:getStrMarshallergetAltStrMarshaller 都是合适的。你想让它做一些不同于固定的marshal的事情吗?
  • 你当然是对的。我忘记将marshalGeneric(GetAltString("me")) 改回marshal(GetAltString("me"))。再次感谢!
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

    但是这种方法有两个问题:

    (1) 在svc.marshal(GetInt("me")) 中,Scala 会将RepImpl 的类型推断为MyReq[Int],这是有道理的,但ReqMarshaller[GetInt, Int] 不匹配。所以你需要将它定义为:

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

    (2) 现在你马上有另一个问题,你不能同时定义两个ReqMarshaller[MyReq[String], String]。也许用相同的类型参数定义两个端点是个坏主意(只是一个猜测,但有些东西不适合这里,它也不适用于 Alexey Romanov 的解决方案)。

    更新

    (1)通过使ReqMarshaller协变来解决:

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

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

    【讨论】:

      猜你喜欢
      • 2014-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-26
      • 1970-01-01
      相关资源
      最近更新 更多