Skip to content

Instantly share code, notes, and snippets.

@harpocrates
Created April 22, 2020 22:27
Show Gist options
  • Save harpocrates/950d58c5cb2af76b5c65227dbd22599d to your computer and use it in GitHub Desktop.
Save harpocrates/950d58c5cb2af76b5c65227dbd22599d to your computer and use it in GitHub Desktop.
Utility for debugging typeclass derivation
import $ivy.`com.chuusai::shapeless:2.3.3`
import $ivy.`io.spray::spray-json:1.3.5`
import shapeless._
import scala.reflect.runtime.universe.WeakTypeTag
import scala.collection.mutable
import spray.json._
sealed trait DataTypeDerivation {
def name: String
def sort: String
def foundInstance: Boolean
def json(stackLimit: Int): JsValue = {
val seenDerivations = mutable.Set.empty[String]
def visitDerivation(dd: DataTypeDerivation, depth: Int): JsValue = {
if (depth > stackLimit)
return JsString(s"Stackoverflow: ${dd.name} at depth ${depth}")
if (!seenDerivations.add(dd.name)) {
JsString(s"Skipping: ${dd.name}")
} else {
val jsFields = Map(
"name" -> JsString(dd.name),
"instance_found" -> JsBoolean(dd.foundInstance),
"type" -> JsString(dd.sort)
)
dd match {
case p: DataTypeDerivation.ProductOf if p.fields.value.nonEmpty =>
val fields = p.fields.value.map(visitDerivation(_, depth + 1))
JsObject(jsFields + ("fields" -> JsArray(fields.toVector)))
case c: DataTypeDerivation.CoproductOf if c.variants.value.nonEmpty =>
val variants = c.variants.value.map(visitDerivation(_, depth + 1))
JsObject(jsFields + ("variants" -> JsArray(variants.toVector)))
case _ =>
JsObject(jsFields)
}
}
}
visitDerivation(this, depth = 0)
}
}
object DataTypeDerivation {
case class ProductOf(
name: String,
fields: Lazy[Seq[DataTypeDerivation]],
foundInstance: Boolean
) extends DataTypeDerivation {
def sort = "product"
}
case class CoproductOf(
name: String,
variants: Lazy[Seq[DataTypeDerivation]],
foundInstance: Boolean
) extends DataTypeDerivation {
def sort = "coproduct"
}
case class Atomic(
name: String,
foundInstance: Boolean
) extends DataTypeDerivation {
def sort = "atomic"
}
}
class OptionalImplicit[A](val value: Option[A])
object OptionalImplicit extends LowPrioityOptionalImplicit {
implicit def found[A](implicit a: A): OptionalImplicit[A] = new OptionalImplicit(Some(a))
}
trait LowPrioityOptionalImplicit {
implicit def notFound[A]: OptionalImplicit[A] = new OptionalImplicit(None)
}
object DebugNestedDerivation {
def apply[F[_], A](implicit d: Derivation[F, A]): DataTypeDerivation = d.derivation
class Derivation[F[_], A](val derivation: DataTypeDerivation)
object Derivation extends LowPriorityDerivation {
implicit def product[F[_], A,G <: HList](implicit
gen: Generic.Aux[A,G],
ct: WeakTypeTag[A],
subs: SubDerivation[F, G],
found: OptionalImplicit[Lazy[F[A]]]
): Derivation[F, A] = new Derivation(DataTypeDerivation.ProductOf(
name = ct.tpe.toString(),
fields = Lazy(subs.derivations),
foundInstance = found.value.nonEmpty
))
implicit def coproduct[F[_], A,G <: Coproduct](implicit
gen: Generic.Aux[A,G],
ct: WeakTypeTag[A],
subs: SubDerivation[F, G],
found: OptionalImplicit[Lazy[F[A]]]
): Derivation[F, A] = new Derivation(DataTypeDerivation.CoproductOf(
name = ct.tpe.toString(),
variants = Lazy(subs.derivations),
foundInstance = found.value.nonEmpty
))
}
trait LowPriorityDerivation {
implicit def atomic[F[_], A](implicit
ct: WeakTypeTag[A],
found: OptionalImplicit[Lazy[F[A]]]
): Derivation[F, A] = new Derivation(DataTypeDerivation.Atomic(
name = ct.tpe.toString(),
foundInstance = found.value.nonEmpty
))
}
trait SubDerivation[F[_], A] {
def derivations: Seq[DataTypeDerivation]
}
object SubDerivation {
implicit def hnilSub[F[_]] = new SubDerivation[F, HNil] {
def derivations = Seq.empty
}
implicit def cnilSub[F[_]] = new SubDerivation[F, CNil] {
def derivations = Seq.empty
}
implicit def hconsSub[F[_], A,B <: HList](implicit
derivation: Lazy[Derivation[F, A]],
rest: SubDerivation[F, B]
): SubDerivation[F, A :: B] = new SubDerivation[F, A :: B] {
def derivations = derivation.value.derivation +: rest.derivations
}
implicit def cconsSub[F[_], A,B <: Coproduct](implicit
derivation: Lazy[Derivation[F, A]],
rest: SubDerivation[F, B]
): SubDerivation[F, A :+: B] = new SubDerivation[F, A :+: B] {
def derivations = derivation.value.derivation +: rest.derivations
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment