我想理解为什么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
答案 0 :(得分:4)
问题在于Scala尝试同时推断ReqImpl
和Resp
类型参数,而不是首先推断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)仍然失败,含糊不清。