Scala在解析隐式类型时如何使用显式类型?

8
我有以下代码,使用spray-json将一些JSON反序列化为一个case类,通过parseJson方法实现。
根据隐式的JsonFormat[MyCaseClass]定义的位置(内联或从伴生对象导入),以及在定义时是否提供了显式类型,代码可能无法编译。
我不明白为什么从伴生对象导入隐式需要定义显式类型,但如果我把它放在内联中,则不需要。
有趣的是,IntelliJ在所有情况下都可以正确定位隐式参数(通过cmd-shift-p)。
我正在使用Scala 2.11.7。
错误代码 - 从伴生对象通配符导入,推断类型:
import SampleApp._
import spray.json._

class SampleApp {
  import MyJsonProtocol._
  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])

  object MyJsonProtocol extends DefaultJsonProtocol {
    implicit val myCaseClassSchemaFormat = jsonFormat1(MyCaseClass)
  }
}

结果为:

Cannot find JsonReader or JsonFormat type class for SampleAppObject.MyCaseClass

请注意,显式导入myCaseClassSchemaFormat隐式参数时也会发生同样的情况。

可用代码 #1 - 从伴生对象通配符导入,显式指定类型:

在伴生对象中为JsonFormat添加一个显式类型可以使代码编译通过:

import SampleApp._
import spray.json._

class SampleApp {
  import MyJsonProtocol._
  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])

  object MyJsonProtocol extends DefaultJsonProtocol {
    //Explicit type added here now
    implicit val myCaseClassSchemaFormat: JsonFormat[MyCaseClass] = jsonFormat1(MyCaseClass)
  }
}

可用代码 #2 - 隐式内联,推断类型:

然而,在使用隐式参数时,将其内联到使用它们的位置且不需要显式指定类型也可以工作!

import SampleApp._
import spray.json._

class SampleApp {
  import DefaultJsonProtocol._

  //Now in-line custom JsonFormat rather than imported
  implicit val myCaseClassSchemaFormat = jsonFormat1(MyCaseClass)

  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])
}

这是其中一个“当我这样做时会受伤”的问题,最好的答案几乎肯定是“那就别这么做”。根据我的经验,在Scala中,缺乏类型注解的隐式值往往是混淆、版本差异等问题的最常见根源之一。 - Travis Brown
嗨Travis - 确实,这是一个有趣的bug需要解决,但我想下次类型注释将是我解决类似问题的首选!不确定这是否算作Scala的bug,但可能会在邮件列表上发布一些内容/考虑提出问题以防万一。 - MrProper
1
编译器会输出一个错误信息,指出“隐式方法 whatever 在此处不适用,因为它位于应用点之后且缺少显式的结果类型”。所以至少这个错误很容易诊断和修复 :) - Hugh
1
华,您在使用以下任何代码示例时是否收到了像这样的错误消息?我只得到了复制粘贴的错误消息:“无法找到SampleAppObject.MyCaseClass的JsonReader或JsonFormat类型类”,但是您的错误消息会更有用。 - MrProper
1个回答

3
在搜索了Huw在评论中提到的错误消息后,我找到了这个来自2010年的StackOverflow问题:为什么Scala方法的显式调用允许它被隐式解析? 这引导我找到了这个Scala问题,它是在2008年创建的,2011年关闭:https://issues.scala-lang.org/browse/SI-801(“是否需要显式指定隐式转换的结果类型?”)
Martin表示:

我实现了一个稍微宽松一点的规则:没有显式结果类型的隐式转换仅在其自身定义后的文本中可见。这样,我们避免了循环引用错误。我暂时关闭,以查看它的工作原理。如果我们仍然有问题,我们可能会回到这个问题。

这意味着 - 如果我重新排列破坏性代码,使得伴生对象首先声明,则代码将编译通过。(仍然有点奇怪!)
(我怀疑我没有看到“隐式方法在此处不适用”的消息,因为我有一个隐式值而不是转换 - 尽管我在这里假设根本原因与上述相同。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接