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
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
The issue is that you are supposed to use implicits with annotation always.
Now, when you use them when they are not annotated you get into some sort on undefined/invalid behavior zone. That is why with unannotated happens sth like this:
Decoder[List[URL]]
Decoder[List[URL]]
implicit in the scopegeneric.auto._
because the definition for that is in a companion object)Decoder[List[URL]]
, so that is inferred type of decodeCompleted
Now, what happens if you annotate?
Decoder[List[URL]]
decodeCompleted
declared as something that fulfills that definitiondecodeCompleted
valuedecodeCompleted
wasn't initialized! in fact we are initializing it right now!decodeCompleted = null
This is virtually equal to:
val decodeCompleted = decodeCompleted
except that the layer of indirection get's in the way of discovering the absurdity of this by compiler. (If you replaced val
with def
you would end up with an infinite recursion and stack overflow):
@ 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)
Yup, its messed up by compiler. You did nothing wrong and in a perfect world it would work.
Scala community mitigates that by distincting:
In the later case, you usually have some utilities like:
import io.circe.generic.semiauto._
implicit val decodeCompleted: Decoder[List[URL]] = deriveDecoder[List[URL]]
It works because it takes DerivedDecoder[A]
implicit and then it extracts Decoder[A]
from it, so you never end up with implicit val a: A = implicitly[A]
scenario.
Indeed the problem is that you introduce a recursive val
, like @MateuszKubuszok explained.
The most straightforward—although slightly ugly—workaround is:
implicit val decodeCompleted: Decoder[List[URL]] = {
val decodeCompleted = null
Decoder[List[URL]].prepare(_.downField("completed"))
}
By shadowing decodeCompleted
in the right-hand side, implicit search will no longer consider it as a candidate inside that code block, because it can no longer be referenced.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With