I faced a problem. I'd like to make a printer for any type in scala. for example i have a case class
class AAA(i: Int, s: String, o: Option[Int], bbb: BBB)
class BBB(l: List[Int])
def explainType[T]: String
and i need a function which can take a type of this class and return a string like
AAA(i: Int, s: String, o: Option(if possible with inner type), bbb:(l: List(if possible with inner type))
I don't really care about format, it needs to be self-explanatory. If it's possible to print it as json, i'd like that. If you know any existing library for that kind of thing, please let me know.
Thanks in advance.
If your classes are case classes you can implement the type class Explain
etc. with Shapeless and Circe
// I'm using Shapeless but still need this macro :)
import scala.language.experimental.macros
import scala.reflect.macros.whitebox // libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
trait ToName[A] {
type Out <: String with Singleton
}
object ToName {
type Aux[A, Out0 <: String with Singleton] = ToName[A] { type Out = Out0 }
implicit def mkToName[A, Out <: String with Singleton]: Aux[A, Out] = macro mkToNameImpl[A]
def mkToNameImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val toNameTpe = weakTypeOf[ToName[A]]
q"""
new $toNameTpe {
type Out = ${A.typeSymbol.name.toString}
}
"""
}
}
// in a different subproject
import io.circe.{Json, JsonObject} // libraryDependencies += "io.circe" %% "circe-core" % "0.14.5"
import shapeless.{::, HList, HNil, LabelledGeneric, Typeable, Witness} // libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.labelled.{FieldType, field}
import shapeless.tag.@@
trait ToNameSymbol[A] {
type Out <: Symbol
}
object ToNameSymbol {
type Aux[A, Out0 <: Symbol] = ToNameSymbol[A] {type Out = Out0}
implicit def mkToNameSymbol[A](implicit toName: ToName[A]): Aux[A, Symbol @@ toName.Out] = null
}
trait LabelledGenericWithName[T <: Product] {
type Repr <: FieldType[_, _ <: HList]
def to(t: T): Repr
def from(r: Repr): T
}
object LabelledGenericWithName {
type Aux[T <: Product, Repr0 <: FieldType[_, _ <: HList]] = LabelledGenericWithName[T] {type Repr = Repr0}
def instance[T <: Product, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new LabelledGenericWithName[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def mkLabelledGenericWithName[T <: Product, L <: HList](implicit
labelledGeneric: LabelledGeneric.Aux[T, L],
toNameSymbol: ToNameSymbol[T]
): Aux[T, FieldType[toNameSymbol.Out, L]] = instance(
t => field[toNameSymbol.Out](labelledGeneric.to(t)),
t => labelledGeneric.from(t)
)
}
trait DeepLabelledGeneric[T <: Product] {
type Repr <: FieldType[_, _ <: HList]
def to(t: T): Repr
def from(r: Repr): T
}
object DeepLabelledGeneric {
type Aux[T <: Product, Repr0 <: FieldType[_, _ <: HList]] = DeepLabelledGeneric[T] {type Repr = Repr0}
def instance[T <: Product, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepLabelledGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def mkDeepLabelledGeneric[A <: Product, K <: Symbol, L <: HList, L1 <: HList](implicit
labelledGenericWithName: LabelledGenericWithName.Aux[A, FieldType[K, L]],
hListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[L, L1]
): Aux[A, FieldType[K, L1]] =
instance(
a => field[K](hListDeepLabelledGeneric.to(labelledGenericWithName.to(a))),
l1 => labelledGenericWithName.from(field[K](hListDeepLabelledGeneric.from(l1)) : FieldType[K, L])
)
}
trait HListDeepLabelledGeneric[T <: HList] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
trait LowPriorityHListDeepLabelledGeneric {
type Aux[T <: HList, Repr0 <: HList] = HListDeepLabelledGeneric[T] {type Repr = Repr0}
def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepLabelledGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def headNotCaseClass[H, T <: HList, T_hListDeepLGen <: HList](implicit
tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
): Aux[H :: T, H :: T_hListDeepLGen] = instance({
case h :: t => h :: tailHListDeepLabelledGeneric.to(t)
}, {
case h :: t => h :: tailHListDeepLabelledGeneric.from(t)
})
}
object HListDeepLabelledGeneric extends LowPriorityHListDeepLabelledGeneric {
implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)
implicit def headCaseClass[K <: Symbol, H <: Product, T <: HList, H_deepLGen <: FieldType[_, _ <: HList], T_hListDeepLGen <: HList](implicit
headDeepLabelledGeneric: DeepLabelledGeneric.Aux[H, H_deepLGen],
tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
): Aux[FieldType[K, H] :: T, FieldType[K, H_deepLGen] :: T_hListDeepLGen] = instance({
case h :: t => field[K](headDeepLabelledGeneric.to(h)) :: tailHListDeepLabelledGeneric.to(t)
}, {
case h :: t => field[K](headDeepLabelledGeneric.from(h)) :: tailHListDeepLabelledGeneric.from(t)
})
}
trait Explain[T <: Product] {
def apply(): JsonObject
}
object Explain {
implicit def mkExplain[T <: Product, K <: Symbol, L <: HList](implicit
deepLabelledGeneric: DeepLabelledGeneric.Aux[T, FieldType[K, L]],
hListExplain: HListExplain[L],
witness: Witness.Aux[K]
): Explain[T] = () => JsonObject(witness.value.name -> Json.fromJsonObject(hListExplain()))
}
trait HListExplain[L <: HList] {
def apply(): JsonObject
}
object HListExplain {
implicit val hNil: HListExplain[HNil] = () => JsonObject()
implicit def headNotHList[K <: Symbol, H, T <: HList](implicit
tailHListExplain: HListExplain[T],
witness: Witness.Aux[K],
typeable: Typeable[H]
): HListExplain[FieldType[K, H] :: T] =
() => (witness.value.name -> Json.fromString(typeable.describe)) +: tailHListExplain()
implicit def headHList[K <: Symbol, K1 <: Symbol, H <: HList, T <: HList](implicit
headHListExplain: HListExplain[H],
tailHListExplain: HListExplain[T],
witness: Witness.Aux[K],
witness1: Witness.Aux[K1],
): HListExplain[FieldType[K, FieldType[K1, H]] :: T] =
() => (witness.value.name -> Json.obj(witness1.value.name -> Json.fromJsonObject(headHListExplain()))) +: tailHListExplain()
}
def explainType[T <: Product](implicit explain: Explain[T]): Json = Json.fromJsonObject(explain())
case class AAA(i: Int, s: String, o: Option[Int], bbb: BBB)
case class BBB(l: List[Int])
explainType[AAA]
//{
// "AAA" : {
// "i" : "Int",
// "s" : "String",
// "o" : "Option[Int]",
// "bbb" : {
// "BBB" : {
// "l" : "List[Int]"
// }
// }
// }
//}
Shapeless - How to derive LabelledGeneric for Coproduct (ToName
)
Deriving nested shapeless lenses using only a type (DeepGeneric
)
Getting Case Class definition which points to another Case Class (DeepLabelledGeneric
)
Scala case classes and recursive reflection (DeepGeneric
)
Get case class field's name and type with shapeless
How to get field names and field types from a Generic Type in Scala?
How to get the name of a class as a string literal at compile time using shapeless?
If the classes are not necessarily case classes you can implement the type class Explain
etc. with macros. Namely, you can replace standard shapeless.Generic
, LabelledGeneric
, DefaultSymbolicLabelling
with the implementations below working not only with case classes. Also, comparing with the above implementation, I'm replacing the upper bound <: Product
with a type class (context bound) IsCaseClassLike
. Otherwise, if I just remove <: Product
and do not add any constraint then the compiler starts to look for generic representation of standard classes like Int
, String
etc.
import shapeless.{Annotation, DepFn0, HList, Refute}
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait ToName[A] {
type Out <: String with Singleton
}
object ToName {
type Aux[A, Out0 <: String with Singleton] = ToName[A] { type Out = Out0 }
implicit def mkToName[A, Out <: String with Singleton]: Aux[A, Out] = macro mkToNameImpl[A]
def mkToNameImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val toNameTpe = weakTypeOf[ToName[A]]
q"""
new $toNameTpe {
type Out = ${A.typeSymbol.name.toString}
}
"""
}
}
// class data extends StaticAnnotation
trait IsCaseClassLike[T]
object IsCaseClassLike {
// nothing should be converted to HList except classes annotated with @data
// implicit def mkIsCaseClassLike[T](implicit ev: Annotation[data, T]): IsCaseClassLike[T] = null
// everything should be converted to HList except standard classes
implicit def mkIsCaseClassLike[T](implicit ev: Refute[IsStandard[T]]): IsCaseClassLike[T] = null
}
trait IsStandard[T]
object IsStandard {
implicit def mkIsStandard[T]: IsStandard[T] = macro mkIsStandardImpl[T]
def mkIsStandardImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val symbol = symbolOf[T]
if (Seq("scala.", "java.", "shapeless.").exists(symbol.fullName.startsWith)) q"null"
else c.abort(c.enclosingPosition, s"$symbol is not standard class")
}
// implicit val int: IsStandard[Int] = null
// implicit val str: IsStandard[String] = null
// implicit def opt[A]: IsStandard[Option[A]] = null
// implicit def list[A]: IsStandard[List[A]] = null
// implicit def hlist[L <: HList]: IsStandard[L] = null
}
trait Generic[T] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
object Generic {
type Aux[T, Repr0 <: HList] = Generic[T] {type Repr = Repr0}
def instance[T, R <: HList](f: T => R, g: R => T): Aux[T, R] = new Generic[T] {
override type Repr = R
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def mkGeneric[T, R <: HList](implicit
ev: IsCaseClassLike[T]
): Aux[T, R] = macro mkGenericImpl[T]
def mkGenericImpl[T : c.WeakTypeTag](c: whitebox.Context)(ev: c.Tree): c.Tree = {
import c.universe._
val T = weakTypeOf[T]
val genTpe = weakTypeOf[Generic[T]]
val fieldTypes = T.decls.filter(s => s.isTerm && !s.isMethod).map(_.typeSignature)
val range = 0 until fieldTypes.size
val names = range.map(i => q"${TermName(s"arg$i")}")
val namePatterns = range.map(i => pq"${TermName(s"arg$i")}")
val sh = q"_root_.shapeless"
val (reprTpe, repr) =
fieldTypes.zip(names)
.foldRight[(Tree, Tree)]((tq"$sh.HNil", q"$sh.HNil")) { case ((tpe, name), (accTpe, acc)) =>
val accTpe1 = tq"$sh.::[$tpe, $accTpe]"
(accTpe1, q"new $accTpe1($name, $acc)")
}
val reprPattern = namePatterns.foldRight[Tree](pq"$sh.HNil")((name, acc) => pq"$sh.::($name, $acc)")
val reprCase = cq"$reprPattern => new $T(..$names)"
val to =
if (T.companion != NoType && T.companion.decl(TermName("unapply")) != NoSymbol) {
val classCase = cq"${T.typeSymbol.companion}(..$namePatterns) => $repr"
q"t match { case $classCase }"
} else q"_root_.scala.Predef.???"
val from = q"r match { case $reprCase }"
q"""
new $genTpe {
override type Repr = $reprTpe
override def to(t: $T): Repr = $to
override def from(r: Repr): $T = $from
}
"""
}
}
trait DefaultSymbolicLabelling[T] extends DepFn0 {
type Out <: HList
}
object DefaultSymbolicLabelling {
type Aux[T, Out0 <: HList] = DefaultSymbolicLabelling[T] {type Out = Out0}
implicit def mkDefaultSymbolicLabelling[T, Out <: HList](implicit
ev: IsCaseClassLike[T]
): Aux[T, Out] = macro mkDefaultSymbolicLabellingImpl[T]
def mkDefaultSymbolicLabellingImpl[T: c.WeakTypeTag](c: whitebox.Context)(ev: c.Tree): c.Tree = {
import c.universe._
val T = weakTypeOf[T]
val dslTpe = weakTypeOf[DefaultSymbolicLabelling[T]]
val fieldNames = T.decls.filter(s => s.isTerm && !s.isMethod).map(_.name.toString.stripSuffix(" "))
val sh = q"_root_.shapeless"
val Sym = q"_root_.scala.Symbol"
val SymT = tq"_root_.scala.Symbol"
val (outTpe, out) =
fieldNames.foldRight[(Tree, Tree)]((tq"$sh.HNil", q"$sh.HNil")) { case (name, (accTpe, acc)) =>
val accTpe1 = tq"$sh.::[$sh.tag.@@[$SymT, $name], $accTpe]"
(
accTpe1,
q"""
new $accTpe1(
$sh.tag.apply[$name].apply[$SymT]($Sym.apply($name)),
$acc
)
"""
)
}
q"""
new $dslTpe {
override type Out = $outTpe
override def apply(): Out = $out
}
"""
}
}
import io.circe.{Json, JsonObject}
import shapeless.{::, HList, HNil, Typeable, Witness, Unpack2}
import shapeless.labelled.{FieldType, KeyTag, field}
import shapeless.ops.hlist.ZipWithKeys
import shapeless.tag.@@
trait LabelledGeneric[T] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
object LabelledGeneric {
type Aux[T, Repr0 <: HList] = LabelledGeneric[T] {type Repr = Repr0}
def instance[T, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new LabelledGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def mkLabelledGeneric[T, K <: HList, V <: HList, R <: HList](implicit
ev0: IsCaseClassLike[T],
lab: DefaultSymbolicLabelling.Aux[T, K],
gen: Generic.Aux[T, V],
zip: ZipWithKeys.Aux[K, V, R],
ev: R <:< V
): Aux[T, R] = instance(t => zip(gen.to(t)), gen.from(_))
}
trait ToNameSymbol[A] {
type Out <: Symbol
}
object ToNameSymbol {
type Aux[A, Out0 <: Symbol] = ToNameSymbol[A] {type Out = Out0}
implicit def mkToNameSymbol[A](implicit
toName: ToName[A]
): Aux[A, Symbol @@ toName.Out] = null
}
trait LabelledGenericWithName[T] {
type Repr <: FieldType[_, _ <: HList]
def to(t: T): Repr
def from(r: Repr): T
}
object LabelledGenericWithName {
type Aux[T, Repr0 <: FieldType[_, _ <: HList]] = LabelledGenericWithName[T] {type Repr = Repr0}
def instance[T, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new LabelledGenericWithName[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def mkLabelledGenericWithName[T, L <: HList](implicit
ev: IsCaseClassLike[T],
labelledGeneric: LabelledGeneric.Aux[T, L],
toNameSymbol: ToNameSymbol[T]
): Aux[T, FieldType[toNameSymbol.Out, L]] =
instance(
t => field[toNameSymbol.Out](labelledGeneric.to(t)),
t => labelledGeneric.from(t)
)
}
trait DeepLabelledGeneric[T] {
type Repr <: FieldType[_, _ <: HList]
def to(t: T): Repr
def from(r: Repr): T
}
object DeepLabelledGeneric {
type Aux[T, Repr0 <: FieldType[_, _ <: HList]] = DeepLabelledGeneric[T] {type Repr = Repr0}
def instance[T, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepLabelledGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def mkDeepLabelledGeneric[A, K <: Symbol, L <: HList, L1 <: HList](implicit
ev: IsCaseClassLike[A],
labelledGenericWithName: LabelledGenericWithName.Aux[A, FieldType[K, L]],
hListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[L, L1]
): Aux[A, FieldType[K, L1]] =
instance(
a => field[K](hListDeepLabelledGeneric.to(labelledGenericWithName.to(a))),
l1 => labelledGenericWithName.from(field[K](hListDeepLabelledGeneric.from(l1)) : FieldType[K, L])
)
}
trait HListDeepLabelledGeneric[T <: HList] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
trait LowPriorityHListDeepLabelledGeneric {
type Aux[T <: HList, Repr0 <: HList] = HListDeepLabelledGeneric[T] {type Repr = Repr0}
def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepLabelledGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def headNotCaseClass[H, T <: HList, T_hListDeepLGen <: HList](implicit
tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
): Aux[H :: T, H :: T_hListDeepLGen] = instance({
case h :: t => h :: tailHListDeepLabelledGeneric.to(t)
}, {
case h :: t => h :: tailHListDeepLabelledGeneric.from(t)
})
}
object HListDeepLabelledGeneric extends LowPriorityHListDeepLabelledGeneric {
implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)
implicit def headCaseClass[K <: Symbol, H, T <: HList, H_deepLGen <: FieldType[_, _ <: HList], T_hListDeepLGen <: HList](implicit
ev: IsCaseClassLike[H],
headDeepLabelledGeneric: DeepLabelledGeneric.Aux[H, H_deepLGen],
tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
): Aux[FieldType[K, H] :: T, FieldType[K, H_deepLGen] :: T_hListDeepLGen] = instance({
case h :: t => field[K](headDeepLabelledGeneric.to(h)) :: tailHListDeepLabelledGeneric.to(t)
}, {
case h :: t => field[K](headDeepLabelledGeneric.from(h)) :: tailHListDeepLabelledGeneric.from(t)
})
}
trait Explain[T] {
def apply(): JsonObject
}
object Explain {
implicit def mkExplain[T, /*FT <: FieldType[_, _ <: HList],*/ K <: Symbol, L <: HList](implicit
ev: IsCaseClassLike[T],
// I was afraid that .Aux[T, FieldType[K, L]] will be over-constrained implicit, see (*)
deepLabelledGeneric: DeepLabelledGeneric.Aux[T, FieldType[K, L]], //DeepLabelledGeneric.Aux[T, FT],
// ev1: Unpack2[FT, FieldType, K, L], // FT <:< FieldType[K, L], // FT =:= FieldType[K, L], // FT <:< KeyTag[K, L],
hListExplain: HListExplain[L],
witness: Witness.Aux[K]
): Explain[T] = () => JsonObject(witness.value.name -> Json.fromJsonObject(hListExplain()))
}
trait HListExplain[L <: HList] {
def apply(): JsonObject
}
object HListExplain {
implicit val hNil: HListExplain[HNil] = () => JsonObject()
implicit def headNotHList[K <: Symbol, H, T <: HList](implicit
tailHListExplain: HListExplain[T],
witness: Witness.Aux[K],
typeable: Typeable[H]
): HListExplain[FieldType[K, H] :: T] =
() => (witness.value.name -> Json.fromString(typeable.describe)) +: tailHListExplain()
implicit def headHList[K <: Symbol, K1 <: Symbol, H <: HList, T <: HList](implicit
headHListExplain: HListExplain[H],
tailHListExplain: HListExplain[T],
witness: Witness.Aux[K],
witness1: Witness.Aux[K1],
): HListExplain[FieldType[K, FieldType[K1, H]] :: T] =
() => (witness.value.name -> Json.obj(witness1.value.name -> Json.fromJsonObject(headHListExplain()))) +: tailHListExplain()
}
def explainType[T](implicit explain: Explain[T]): Json = Json.fromJsonObject(explain())
/*@data*/
/*case*/ class AAA(val i: Int, val s: String, o: Option[Int], bbb: BBB)
/*@data*/
/*case*/ class BBB(l: List[Int])
explainType[AAA]
//{
// "AAA" : {
// "i" : "Int",
// "s" : "String",
// "o" : "Option[Int]",
// "bbb" : {
// "BBB" : {
// "l" : "List[Int]"
// }
// }
// }
//}
(*) Why is this implicit resolution failing? , HList foldLeft with tuple as zero