I made a generic DynamoFormat
for Scanamo
that would put any object that has Circe
's Encoder
and Decoder
defined into database as a Json string.
import com.gu.scanamo.DynamoFormat
import io.circe.parser.parse
import io.circe.syntax._
import io.circe.{Decoder, Encoder}
object JsonDynamoFormat {
def forType[T: Encoder: Decoder]: DynamoFormat[T] = DynamoFormat.coercedXmap[T, String, Exception] {
s => parse(s).flatMap(_.as[T]).fold(err => throw err, obj => obj)
} {
obj => obj.asJson.noSpaces
}
}
Then I added an implicit conversion (to the same object JsonDynamoFormat
) to automatically provide these formatters.
implicit def jsonToFormat[T: Encoder: Decoder]: DynamoFormat[T] = JsonDynamoFormat.forType[T]
When I import it, compiler resolves formatters successfully, however at runtime I get a stack overflow in JsonDynamoFormat
, where calls to jsonToFormat
and forType
alternate infinitely:
Exception in thread "main" java.lang.StackOverflowError
at JsonDynamoFormat$.forType(JsonDynamoFormat.scala:12)
at JsonDynamoFormat$.jsonToFormat(JsonDynamoFormat.scala:9)
at JsonDynamoFormat$.forType(JsonDynamoFormat.scala:13)
at JsonDynamoFormat$.jsonToFormat(JsonDynamoFormat.scala:9)
...
I can't really understand what happens here. Could anyone shed a light on this?
Debugging Scala implicits errors can be quite taxing. Here is a couple of suggestions that can help:
Enable scalacOptions ++= Seq("-Xlog-implicits")
compiler option. This will print implicit search log, and can be useful to understand where exactly the implicit chain breaks.
Add splain libraryDependencies ++= Seq(compilerPlugin("io.tryp" %% "splain" % "0.2.4"))
to improve the implicit debug log readability.
In general, stack overflow at runtime with generically derived typeclasses is a sign of wrong implicit resolution. This usually means compiler has found a couple of circularly dependent implicits and used one of them to satisfy the other one, and vice versa.
Normally such situation is recognized at compile time, and compile produces "diverging implicits" error, but that error can be a false positive, and therefore library authors usually circumvent it by using a technique like Lazy
typeclass from shapeless. However in case of an actual buggy circular implicits, this will result in runtime error, instead of compile time error.