无法绑定[SttpBackend [尝试,一无所有]]

时间:2019-01-25 16:13:07

标签: scala guice

我想在我的应用中将sttp库与guice(带有scalaguice包装器)一起使用。但是似乎正确绑定SttpBackend[Try, Nothing]

之类的东西并不容易

SttpBackend.scala

Try[_]Try[AnyRef]显示其他错误,但仍然不知道如何正确完成操作

我得到的错误:

kinds of the type arguments (scala.util.Try) do not conform to the expected kinds of the type parameters (type T).
[error] scala.util.Try's type parameters do not match type T's expected parameters:
[error] class Try has one type parameter, but type T has none
[error]         bind[SttpBackend[Try, Nothing]].toProvider[SttpBackendProvider]
[error]    `         ^

SttpBackendProvider看起来像:

def get: SttpBackend[Try, Nothing] = TryHttpURLConnectionBackend(opts)

complete example in scastie 有趣的是,版本scalaguice 4.1.0显示了此错误,但最新的4.2.2显示了其中将Nothing转换为JavaType的错误

1 个答案:

答案 0 :(得分:0)

我相信您在Scala-Guice中遇到了两个不同的错误,其中一个尚未修复(甚至可能尚未提交)。

要描述这些问题,我需要快速介绍Guice和Scala-Guice的工作方式。本质上,Guice所做的是对该类型的对象从类型到工厂方法进行映射。为了支持某些高级功能,将类型映射到一些内部“键”表示上,然后针对每个“键”,Guice构建一种构造对应对象的方法。同样重要的是,使用类型擦除来实现Java中的泛型。这就是为什么当您编写类似这样的内容:

bind(classOf[SttpBackend[Try, Nothing]]).toProvider(classOf[SttpBackendProvider])

在原始向导中,“密钥”实际上变成了类似于“ com.softwaremill.sttp.SttpBackend”的东西。幸运的是,Guice开发人员已经考虑了泛型的问题,并介绍了TypeLiteral[T],以便您可以传达有关泛型的信息。

Scala类型的系统比Java具有更大的影响范围,并且它具有来自编译器的更好的反射支持。 Scala-Guice利用它来自动将Scala类型映射到那些更详细的键上。不幸的是,它并不总是能正常工作。

第一个问题是SttpBackend类型被定义为以下事实的结果

trait SttpBackend[R[_], -S]

因此它使用它期望它的第一个参数是类型构造函数;并且最初Scala-Guice使用了scala.reflect.Manifest基础架构。 AFAIU这样的高类型类型无法表示为Manifest,这就是您的问题中的错误所在。

幸运的是,Scala添加了新的scala.reflect.runtime.universe.TypeTag基础结构,以更好,更一致的方式解决了此问题,Scala-Guice迁移到了用法。这就是为什么使用新版本的Scala-Guice可以消除编译器错误的原因。不幸的是,Scala-Guice中还有另一个错误使代码在运行时失败,并且缺乏对Nothing Scala类型的处理。您会看到Nothing类型在JVM上是一种伪造的类型。 Scala类型系统比Java具有更大的影响力是一回事。 JVM世界中没有Nothing的直接映射。幸运的是,无法创建类型为Nothing的任何值。不幸的是,您仍然可以创建一个classOf[Nothing]。 Scala-to-JVM编译器通过使用人工scala.runtime.Nothing$来处理它。它不是公共API的一部分,而是特定于JVM的Scala的实现细节。无论如何,这意味着Nothing类型在转换为Guice TypeLiteral时需要额外的处理,并且没有。 AnyNothing的堂兄,但Nothing没有(见TypeConversions.scalaanyType的用法)。

所以实际上有两种解决方法:

  1. 对Guice使用基于Java的原始语法,而不是漂亮的Scala-Guice之一:
bind(new TypeLiteral[SttpBackend[Try, Nothing]]() {})
    .toInstance(sttpBackend) // or to whatever

根据您的示例,请参见online demo

  1. 在Scala-Guice中修补TypeConversions.scala,如下所示:
private[scalaguice] object TypeConversions {
  private val mirror = runtimeMirror(getClass.getClassLoader)
  private val anyType = typeOf[Any]
  private val nothingType = typeOf[Nothing] // added

  ...

  def scalaTypeToJavaType(scalaType: ScalaType): JavaType = {
    scalaType.dealias match {
      case `anyType` => classOf[java.lang.Object]
      case `nothingType` => classOf[scala.runtime.Nothing$] //added
      ...

我在本地尝试过,看来可以解决您的问题。我没有进行任何广泛的测试,因此它可能破坏了其他功能。