带有io.circe.Decoder的奇怪NPE

时间:2019-05-23 15:23:57

标签: scala circe

我有两个声明如下的变量

"firebase": "4.6.2",
"firebase-admin": "^7.4.0",
"firebase-functions": "^2.2.1",
...
"devDependencies": {
   "firebase-functions-test": "^0.1.6"
},

两行都编译并运行。

但是,如果我用类型 implicit val decodeURL: Decoder[URL] = Decoder.decodeString.emapTry(s => Try(new URL(s))) // #1 implicit val decodeCompleted = Decoder[List[URL]].prepare(_.downField("completed")) // #2 注释#2。它会编译,并且#2将在运行时抛出implicit val decodeCompleted: Decoder[List[URL]] = Decoder[List[URL]].prepare(_.downField("completed"))(NPE)。

这怎么可能发生?我不知道这是Circe还是普通的Scala问题。为什么#2与#1不同?谢谢

2 个答案:

答案 0 :(得分:3)

问题是您应该始终使用带注释的隐式对象。

现在,当您在未注释时使用它们时,您会陷入未定义/无效行为区域的某种分类。这就是为什么未注释的情况是这样的:

  • 我要使用Decoder[List[URL]]
  • 范围内没有隐含的(带注释)Decoder[List[URL]]
  • 正常地派生它(不需要generic.auto._,因为它的定义在同伴对象中)
  • 一旦派生就调用它。.prepare(_。downField(“ completed”))
  • 最终结果的类型为Decoder[List[URL]],因此推断出的类型为decodeCompleted

现在,如果您进行注释会怎样?

  • 我要使用Decoder[List[URL]]
  • decodeCompleted被声明为符合该定义的东西
  • 使用decodeCompleted
  • 但是decodeCompleted尚未初始化!实际上,我们现在正在初始化它!
  • 因此,您最终得到decodeCompleted = null

这实际上等于:

val decodeCompleted = decodeCompleted

除了间接获取层是通过编译器发现它的荒谬之处之外。 (如果将val替换为def,将导致无限递归和堆栈溢出):

@ implicit val s: String = implicitly[String] 
s: String = null

@ implicit def s: String = implicitly[String] 
defined function s

@ s 
java.lang.StackOverflowError
  ammonite.$sess.cmd1$.s(cmd1.sc:1)
  ammonite.$sess.cmd1$.s(cmd1.sc:1)
  ammonite.$sess.cmd1$.s(cmd1.sc:1)
  ammonite.$sess.cmd1$.s(cmd1.sc:1)

是的,编译器将其弄乱了。您没有做错任何事情,在一个完美的世界中,它会起作用。

Scala社区通过与众不同来缓解这种情况:

  • 自动派生-当您需要隐式某处并且无需定义变量即可自动派生
  • 半自动推导-当您推导一个值并隐含该值时

在后一种情况下,通常会有一些实用程序,例如:

import io.circe.generic.semiauto._

implicit val decodeCompleted: Decoder[List[URL]] = deriveDecoder[List[URL]]

之所以有效,是因为它隐含了DerivedDecoder[A],然后从中提取Decoder[A],所以您永远不会遇到implicit val a: A = implicitly[A]的情况。

答案 1 :(得分:1)

实际上,问题在于您引入了递归"?m=0",就像@MateuszKubuszok解释的那样。

最直接(尽管有点难看)的解决方法是:

val

通过在右侧隐藏implicit val decodeCompleted: Decoder[List[URL]] = { val decodeCompleted = null Decoder[List[URL]].prepare(_.downField("completed")) } ,隐式搜索将不再将其视为该代码块内的候选对象,因为无法再对其进行引用。