You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Certain shapes of data are difficult to write correct Decoder/Encoder instances for, and providing a Defer instance for Decoder and Encoder can make this much simpler to do correctly.
Problem Statement
The easiest way to see this is working through an example. I'll use the same data structure and sample JSON for each example:
A naive decoder, written using the examples from the ADT section of the guide doesn't work as expected. Because the implicit definition has to be a def, "tying the knot" doesn't work, and this produces a StackOverflowError at runtime:
This can be worked around by manually short-circuiting the code that uses the implicit lookup, but it's kind of ugly and requires peeking behind the curtain of the cursor implementation:
implicitdefbranchDecoder[A:Decoder]:Decoder[Branch[A]] =newDecoder[Branch[A]] {
overridedefapply(c: HCursor):Result[Branch[A]] = decodeAccumulating(c).toEither.leftMap(_.head)
privatevalnullDecoder=Decoder.failed[Tree[A]](DecodingFailure("Should not see this", Nil))
overridedefdecodeAccumulating(c: HCursor):AccumulatingResult[Branch[A]] = {
(
c.downField("left") match {
casecursor: HCursor=>Decoder[Tree[A]].tryDecodeAccumulating(cursor)
case cursor => nullDecoder.tryDecodeAccumulating(cursor)
},
c.downField("right") match {
casecursor: HCursor=>Decoder[Tree[A]].tryDecodeAccumulating(cursor)
case cursor => nullDecoder.tryDecodeAccumulating(cursor)
}
).mapN(Branch.apply)
}
}
implicitdefleafDecoder[A:Decoder]:Decoder[Leaf[A]] =Decoder[A].at("value").map(Leaf(_))
implicitdeftreeDecoder[A:Decoder]:Decoder[Tree[A]] =List[Decoder[Tree[A]]](
Decoder[Branch[A]].widen,
Decoder[Leaf[A]].widen
).reduce(_ combine _)
This does work, but it has rather nasty runtime behavior: it creates a number of decoders that scales on the number of nodes in Tree. Instrumenting this with counts of calls to branchDecoder, leafDecoder and treeDecoder reveal that decoding Tree.json builds 21 total decoders (7 of each type).
Wrapping the implementation of treeDecoder in another Decoder[Tree] that provides itself to the current implementation and delegates to the same solves the problem of excessive instantiations:
As Defer abstracts this solution in a much cleaner package, providing instances for Decoder and Encoder would allow the working version we arrived at like this:
Because Defer doesn't have great visibility, I'd recommend doing what cats-parse does, and provide Decoder.recursive to lead users to the correct implementation, which would look like this:
Certain shapes of data are difficult to write correct
Decoder
/Encoder
instances for, and providing aDefer
instance forDecoder
andEncoder
can make this much simpler to do correctly.Problem Statement
The easiest way to see this is working through an example. I'll use the same data structure and sample JSON for each example:
Attempt 1: Write it by the book
A naive decoder, written using the examples from the
ADT
section of the guide doesn't work as expected. Because the implicit definition has to be adef
, "tying the knot" doesn't work, and this produces aStackOverflowError
at runtime:scastie
Attempt 2: Force it to be lazy
This can be worked around by manually short-circuiting the code that uses the implicit lookup, but it's kind of ugly and requires peeking behind the curtain of the cursor implementation:
This does work, but it has rather nasty runtime behavior: it creates a number of decoders that scales on the number of nodes in
Tree
. Instrumenting this with counts of calls tobranchDecoder
,leafDecoder
andtreeDecoder
reveal that decodingTree.json
builds 21 total decoders (7 of each type).scastie
Attempt 3: Implicitly self-referential class
Wrapping the implementation of
treeDecoder
in anotherDecoder[Tree]
that provides itself to the current implementation and delegates to the same solves the problem of excessive instantiations:scastie
Solution
As
Defer
abstracts this solution in a much cleaner package, providing instances forDecoder
andEncoder
would allow the working version we arrived at like this:Because
Defer
doesn't have great visibility, I'd recommend doing whatcats-parse
does, and provideDecoder.recursive
to lead users to the correct implementation, which would look like this:The text was updated successfully, but these errors were encountered: