Strange NPE with io.circe.Decoder

thl*_*lim 6 scala circe

I have 2 variables declared as follows,

 implicit val decodeURL: Decoder[URL] = Decoder.decodeString.emapTry(s => Try(new URL(s)))     // #1

 implicit val decodeCompleted = Decoder[List[URL]].prepare(_.downField("completed")) // #2
Run Code Online (Sandbox Code Playgroud)

Both lines compile and run.

However, if I annotate #2 with type i.e. implicit val decodeCompleted: Decoder[List[URL]] = Decoder[List[URL]].prepare(_.downField("completed")). It compiles and #2 will throw NullPointerException (NPE) during runtime.

How could this happen? I don't know if this is Circe or just plain Scala issue. Why #2 is different from #1? Thanks

Mat*_*zok 4

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

现在,当您在没有注释的情况下使用它们时,您会进入某种未定义/无效的行为区域。这就是为什么在未注释的情况下会发生这样的事情:

  • 我想用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
Run Code Online (Sandbox Code Playgroud)

只是间接层妨碍了编译器发现这种做法的荒谬性。(如果替换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)

Run Code Online (Sandbox Code Playgroud)

是的,它被编译器弄乱了。你没有做错任何事,在完美的世界里它会起作用。

Scala 社区通过区分以下内容来缓解这一问题:

  • 自动派生 - 当您需要隐式某处并且它会自动派生而无需为其定义变量时
  • 半自动推导 - 当您推导一个值并使该值隐式时

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

import io.circe.generic.semiauto._

implicit val decodeCompleted: Decoder[List[URL]] = deriveDecoder[List[URL]]
Run Code Online (Sandbox Code Playgroud)

它之所以有效,是因为它需要DerivedDecoder[A]隐式,然后从中提取Decoder[A],所以你永远不会以场景结束implicit val a: A = implicitly[A]