Suppose I'd like to traverse case class generic representation as described here
I've defined some typeclass to describe fields:
trait Described[X] extends (X => String)
object Described{
def apply[X](x: X)(implicit desc: Described[X]) = desc(x)
}
Defined some instance:
implicit object DoubleDescribed extends Described[Double]{
def apply(x: Double) = x.formatted("%01.3f")
}
And general user:
import shapeless._
import shapeless.labelled.FieldType
import shapeless.ops.hlist.LeftFolder
object DescrFolder extends Poly2{
implicit def field[X, S <: Symbol](implicit desc: Described[X],
witness: Witness.Aux[S]):
Case.Aux[Seq[String], FieldType[S, X], Seq[String]] =
at[Seq[String], FieldType[S, X]](
(descrs, value) => descrs :+ f"${witness.value.name}: ${desc(value)}")
}
def describe[T <: Product, Repr <: HList](struct: T)
(implicit lgen: LabelledGeneric.Aux[T,Repr],
folder: LeftFolder.Aux[Repr, Seq[String], DescrFolder.type, Seq[String]]
): String = {
val repr = lgen.to(struct)
val descrs = folder(repr,Vector())
descrs.mkString(struct.productPrefix + "{", ",", "}")
}
So now i could write
case class Point(x: Double, y: Double, z: Double)
describe(Point(1,2,3.0))
and get
res1: String = Point{x: 1,000,y: 2,000,z: 3,000}
Now i'd like to define some field metadata using shapeless
tags:
import tag._
trait Invisible
val invisible = tag[Invisible]
implicit def invisibleDescribed[X](implicit desc: Described[X])
: Described[X @@ Invisible] =
new Described[X @@ Invisible]{
def apply(x: X @@ Invisible) = desc(x: X) + "[invisible]"
}
so Described(invisible(0.5))
now succesfully produces
res2: String = 0,500[invisible]
But with redefined
case class Point(x: Double, y: Double, z: Double @@ Invisible)
describe(Point(1,2,invisible(3.0)))
yields compilation error:
Error: diverging implicit expansion for type
LeftFolder.Aux[this.Out,Seq[String],DescrFolder.type,Seq[String]]
starting with methodinvisibleDescribed
in class ...
I presume that type X with Tag[Y] with KeyTag[K,X]
is not identifying as FieldType[S, X]
but could not guess how to fix it.
How could one define proper LeftFolder
for such situation?
Your issue does not involve
shapeless
at all. It can be actually simplified as:Double @@ Invisible
can be "represented" asDouble with Invisible
. Note thatDouble with Invisible <: Double
.When the compiler tries to get an implicit
Described[Double with Invisible]
it correctly complains about diverging implicit expansion:doubleDescribed
andinvisibleDescribed
.Going back to your original code, an easy fix could be to just rewrite
invisibleDescribed
as: