io.circe.Decoder 的奇怪 NPE
Strange NPE with io.circe.Decoder
我声明了2个变量,
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,即 implicit val decodeCompleted: Decoder[List[URL]] = Decoder[List[URL]].prepare(_.downField("completed"))
。它编译并且#2 将在 运行 时间内抛出 NullPointerException
(NPE)。
怎么会这样?我不知道这是 Circe 还是普通的 Scala 问题。为什么#2 与#1 不同?谢谢
问题是您应该始终使用带注释的隐式。
现在,当您在未注释的情况下使用它们时,您会进入某种 undefined/invalid 行为区。这就是为什么在未注释的情况下会发生这样的事情:
- 我想用
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.WhosebugError
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]
场景。
确实问题是你引入了一个递归 val
,就像@MateuszKubuszok 解释的那样。
最直接——虽然有点难看——的解决方法是:
implicit val decodeCompleted: Decoder[List[URL]] = {
val decodeCompleted = null
Decoder[List[URL]].prepare(_.downField("completed"))
}
通过在 right-hand 侧隐藏 decodeCompleted
,隐式搜索将不再将其视为该代码块内的候选者,因为它不再被引用。
我声明了2个变量,
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,即 implicit val decodeCompleted: Decoder[List[URL]] = Decoder[List[URL]].prepare(_.downField("completed"))
。它编译并且#2 将在 运行 时间内抛出 NullPointerException
(NPE)。
怎么会这样?我不知道这是 Circe 还是普通的 Scala 问题。为什么#2 与#1 不同?谢谢
问题是您应该始终使用带注释的隐式。
现在,当您在未注释的情况下使用它们时,您会进入某种 undefined/invalid 行为区。这就是为什么在未注释的情况下会发生这样的事情:
- 我想用
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.WhosebugError
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]
场景。
确实问题是你引入了一个递归 val
,就像@MateuszKubuszok 解释的那样。
最直接——虽然有点难看——的解决方法是:
implicit val decodeCompleted: Decoder[List[URL]] = {
val decodeCompleted = null
Decoder[List[URL]].prepare(_.downField("completed"))
}
通过在 right-hand 侧隐藏 decodeCompleted
,隐式搜索将不再将其视为该代码块内的候选者,因为它不再被引用。