我遇到了一个问题。我想为 Scala 中的任何类型制作一台打印机。 例如我有一个案例类
class AAA(i: Int, s: String, o: Option[Int], bbb: BBB)
class BBB(l: List[Int])
def explainType[T]: String
我需要一个函数,它可以采用此类的类型并返回类似的字符串
AAA(i: Int, s: String, o: Option(if possible with inner type), bbb:(l: List(if possible with inner type))
我不太关心格式,它需要不言自明。如果可以将其打印为 json,我会喜欢的。 如果你知道任何现有的图书馆,请告诉我。
提前致谢。
如果你的类是 case 类,你可以使用
Shapeless和 Circe 实现类型类
Explain
等
// 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 - 如何为 Coproduct 推导 LabelledGeneric (
ToName
)
DeepGeneric
)
DeepLabelledGeneric
)
DeepGeneric
)
如何在编译时使用 shapeless 获取类名作为字符串文字?
如果类不一定是案例类,您可以使用
macros实现类型类
Explain
等。也就是说,您可以将标准的 shapeless.Generic
、LabelledGeneric
、DefaultSymbolicLabelling
替换为以下不仅适用于案例类的实现。另外,与上面的实现相比,我将上限<: Product
替换为类型类(上下文绑定)IsCaseClassLike
。否则,如果我只是删除 <: Product
并且不添加任何约束,那么编译器将开始寻找标准类的通用表示形式,例如 Int
、String
等
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]"
// }
// }
// }
//}
如果您使用的是 Scala 3(又名 dotty),这里有一个更简单的解决方案,无需依赖任何第三方库。
用例:
λ scalac Macros.scala
λ scala -cp .
Welcome to Scala 3.2.0-RC2.
Type in expressions for evaluation. Or try :help.
scala> import Macros.explainType
scala> explainType[(Int, Double, String)]
val res0: String = (Int, Double, String)
scala> explainType[List[Option[Int]]]
val res1: String = List[Option[Int]]
scala> case class BBB(l: List[Int])
// defined case class BBB
scala> case class AAA(i: Int, s: String, o: Option[Int], bbb: BBB)
// defined case class AAA
scala> explainType[BBB]
val res2: String = BBB(l :List[Int])
scala> explainType[AAA]
val res3: String = AAA(i :Int, s :String, o :Option[Int], bbb :BBB(l :List[Int]))
这里是实现:
// Save it as Macros.scala
object Macros:
import scala.compiletime.*
import scala.deriving.Mirror
import scala.quoted.*
type Head[T] = T match
case h *: _ => h
type Tail[T] = T match
case _ *: t => t
def simpleNameImp[A](using Type[A], Quotes): Expr[String] =
Expr(Type.show[A].replaceAll("""(scala|java)\.([$\w]+\.)*""", ""))
inline def simpleName[A]: String = ${ simpleNameImp[A] }
def isInnerTypeImp[A](using Type[A], Quotes): Expr[Boolean] =
Expr(Type.show[A].startsWith("scala.") || Type.show[A].startsWith("java."))
inline def isInnerType[A]: Boolean = ${ isInnerTypeImp[A] }
inline def tupleName[T]: List[String] = inline erasedValue[T] match
case EmptyTuple => Nil
case _ => explainType[Head[T]] :: tupleName[Tail[T]]
inline def explainType[A]: String = inline erasedValue[A] match
case _: Tuple => tupleName[A].mkString("(", ", ", ")")
case _ if isInnerType[A] => simpleName[A]
case _ => summonFrom {
case m: Mirror.Of[A] =>
val name = constValue[m.MirroredLabel]
val labels = constValueTuple[m.MirroredElemLabels]
val types = tupleName[m.MirroredElemTypes]
name + (labels.toArray zip types).map { (lab, typ) => s"$lab :$typ" }.mkString("(", ", ", ")")
case _ => simpleName[A]
}