Using shapeless tags with LabelledGenerics
Asked Answered
I

1

8

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 method invisibleDescribed 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?

Isborne answered 27/8, 2015 at 13:33 Comment(0)
C
0

Your issue does not involve shapeless at all. It can be actually simplified as:

trait Described[T]
trait Invisible

implicit val doubleDescribed: Described[Double] = ???

implicit def invisibleDescribed[T](
  implicit desc: Described[T]
): Described[T with Invisible] = ???

implicitly[Described[Double with Invisible]]

Double @@ Invisible can be "represented" as Double with Invisible. Note that Double with Invisible <: Double.

When the compiler tries to get an implicit Described[Double with Invisible] it correctly complains about diverging implicit expansion: doubleDescribed and invisibleDescribed.

Going back to your original code, an easy fix could be to just rewrite invisibleDescribed as:

implicit def invisibleDescribed[X, I <: X @@ Invisible](
  implicit desc: Described[X]
): Described[I] = new Described[I]{
  def apply(x: I) = desc(x: X) + "[invisible]"
}
Complemental answered 4/4, 2017 at 7:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.