无法绑定 [SttpBackend [尝试,无]]

can`t bind[SttpBackend[Try, Nothing]]

我想在我的应用程序中使用带有 guice(带有 scalaguice 包装器)的 sttp 库。但似乎正确绑定 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

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

为了描述这些问题,我需要快速介绍一下 Guice 和 Scala-Guice 的工作原理。本质上,Guice 所做的是将类型映射到该类型对象的工厂方法。为了支持一些高级功能,类型被映射到一些内部 "keys" 表示,然后为每个 "key" Guice 构建一种构造相应对象的方法。同样重要的是 Java 中的泛型是使用类型擦除实现的。这就是为什么当你写这样的东西时:

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

在 raw-Guice 中,"key" 实际上变成了类似 "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 infrastructure to tackle this issue in a better and more consistent way and the Scala-Guice migrated to its usage. That's why with the newer version of Scala-Guice the compiler error goes away. Unfortunately there is another bug in the Scala-Guice that makes the code fail in runtime and it is a lack of handling of the Nothing Scala type. You see, the Nothing type is a kind of fake one on the JVM. It is one of the things where the Scala type system is more reach than the Java one. There is no direct mapping for Nothing in the JVM world. Luckily there is no way to create any value of the type Nothing. Unfortunately you still can create a classOf[Nothing]. The Scala-to-JVM compiler handles it by using an artificial scala.runtime.Nothing$. It is not a part of the public API, it is implementation details of specifically Scala over JVM. Anyway this means that the Nothing type needs additional handling when converting into the Guice TypeLiteral and there is none. There is for Any the cousin of Nothing but not for Nothing (see the usage of the anyType in TypeConversions.scala).

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

  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
      ...

我在本地试过了,它似乎修复了你的例子。我没有做任何广泛的测试,所以它可能破坏了其他东西。