我正在尝试为 Coproduct 生成 LabelledGeneric,以便可以使用它来代替典型的
sealed trait
层次结构。到目前为止,我可以通过明确指定 DefaultSymbolicLabelling
的标签来做到这一点,但我觉得应该可以从联积的类型成员中自动派生它。
/**
* So far I found no way to derive `L` and `l` from `C`.
*/
object ShapelessLabelledGenericForCoproduct extends App {
trait Base // not sealed!
case class Case1(a: Int) extends Base
case class Case2(a: String) extends Base
case class Case3(b: Boolean) extends Base
object Base {
type C = Case1 :+: Case2 :+: Case3 :+: CNil
type L = (Symbol @@ "Case1") :: (Symbol @@ "Case2") :: (Symbol @@ "Case3") :: shapeless.HNil
val l: L = tag["Case1"](Symbol("Case1")) :: tag["Case2"](Symbol("Case2")) :: tag["Case3"](Symbol("Case3")) :: HNil
implicit def myGeneric: Generic.Aux[Base, C] = Generic.instance[Base, C](
v => Coproduct.runtimeInject[C](v).get,
v => Coproduct.unsafeGet(v).asInstanceOf[Base]
)
implicit def mySymbolicLabelling: DefaultSymbolicLabelling.Aux[Base, L] = DefaultSymbolicLabelling.instance[Base, L](l)
}
val lgen = LabelledGeneric[Base]
val repr = lgen.to(Case1(123))
println(lgen.from(repr))
}
见下面带有密封特性的代码;一般来说,我想实现类似的行为,只是不密封特征。
object ShapelessLabelledGenericForSealedTrait extends App {
sealed trait Base
case class Case1(a: Int) extends Base
case class Case2(a: String) extends Base
case class Case3(b: Boolean) extends Base
val lgen = LabelledGeneric[Base]
val repr = lgen.to(Case1(123))
println(lgen.from(repr))
}
有什么提示吗?通过无形宏查看,但到目前为止我没有发现任何有用的东西......
米
对于非
sealed
trait,Shapeless 中定义的Generic
/LabelledGeneric
的实例不能工作
所有此类宏都使用
.knownDirectSubclasses
。它仅适用于密封特征。
Scala 反射:knownDirectSubclasses 仅适用于密封特征?
对于未密封的特征,我总是可以在不同的文件(
Base
)甚至在运行时(case class Case4() extends Base
)添加toolbox.define(q"case class Case4() extends Base")
的继承者。
如果您只对当前文件中定义的继承者感兴趣,那么也许您可以避免使用
.knownDirectSubclasses
并编写一个宏遍历当前文件的 AST 并寻找继承者。
到目前为止,我找不到从
中推导出L
和l
的方法。C
不难
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]
q"""
new ToName[$A] {
type Out = ${A.typeSymbol.name.toString}
}
"""
}
}
implicitly[ToName.Aux[Case1, "Case1"]] // compiles
import shapeless.ops.coproduct.ToHList
import shapeless.tag.@@
import shapeless.{:+:, ::, CNil, HList, HNil, Poly0, Poly1, Witness, tag, the}
object toNamePoly extends Poly1 {
implicit def cse[A <: Base, S <: String with Singleton](implicit
toName: ToName.Aux[A, S],
witness: Witness.Aux[S],
// valueOf: ValueOf[S],
): Case.Aux[A, Symbol @@ S] = at(_ => tag[S](Symbol(witness/*valueOf*/.value)))
}
object nullPoly extends Poly0 {
implicit def default[A]: Case0[A] = at(null.asInstanceOf[A])
}
val res = HList.fillWith[the.`ToHList[C]`.Out](nullPoly).map(toNamePoly)
res: L // compiles
res == l // true
所以你可以推导出
DefaultSymbolicLabelling
如下
import shapeless.ops.coproduct.ToHList
import shapeless.ops.hlist.{FillWith, Mapper}
implicit def mySymbolicLabelling[L <: HList](implicit
toHList: ToHList.Aux[C, L],
fillWith: FillWith[nullPoly.type, L],
mapper: Mapper[toNamePoly.type, L],
): DefaultSymbolicLabelling.Aux[Base, mapper.Out] =
DefaultSymbolicLabelling.instance[Base, mapper.Out](mapper(fillWith()))
这是遍历的代码。我在介绍类型课
KnownSubclasses
import shapeless.Coproduct
import scala.collection.mutable
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait KnownSubclasses[A] {
type Out <: Coproduct
}
object KnownSubclasses {
type Aux[A, Out0 <: Coproduct] = KnownSubclasses[A] { type Out = Out0 }
implicit def mkKnownSubclasses[A, Out <: Coproduct]: Aux[A, Out] = macro mkKnownSubclassesImpl[A]
def mkKnownSubclassesImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
var children = mutable.Seq[Type]()
// subclasses of A
val traverser = new Traverser {
override def traverse(tree: Tree): Unit = {
tree match {
case _: ClassDef =>
val tpe = tree.symbol.asClass.toType
if (tpe <:< A && !(tpe =:= A)) children :+= tpe
case _ =>
}
super.traverse(tree)
}
}
// def getType(t: Tree): Type = c.typecheck(tq"$t", mode = c.TYPEmode).tpe
//
// // direct subclasses of A
// val traverser = new Traverser {
// override def traverse(tree: Tree): Unit = {
// tree match {
// case q"$_ class $_[..$_] $_(...$_) extends { ..$_ } with ..$parents { $_ => ..$_ }"
// if parents.exists(getType(_) =:= A) =>
// children :+= tree.symbol.asClass.toType
// case _ =>
// }
//
// super.traverse(tree)
// }
// }
c.enclosingRun.units.foreach(unit => traverser.traverse(unit.body))
val coprod = children.foldRight[Tree](tq"_root_.shapeless.CNil")((child, copr) => tq"_root_.shapeless.:+:[$child, $copr]")
q"""
new KnownSubclasses[$A] {
type Out = $coprod
}
"""
}
}
implicitly[KnownSubclasses.Aux[Base, Case1 :+: Case2 :+: Case3 :+: CNil]] // compiles
所以你可以推导出
Generic
和DefaultSymbolicLabelling
(因此是LabelledGeneric
)如下
import shapeless.ops.coproduct.{RuntimeInject, ToHList}
import shapeless.ops.hlist.{FillWith, Mapper}
implicit def myGeneric[C <: Coproduct](implicit
knownSubclasses: KnownSubclasses.Aux[Base, C],
runtimeInject: RuntimeInject[C]
): Generic.Aux[Base, C] = Generic.instance[Base, C](
v => Coproduct.runtimeInject[C](v).get,
v => Coproduct.unsafeGet(v).asInstanceOf[Base]
)
implicit def mySymbolicLabelling[C <: Coproduct, L <: HList](implicit
knownSubclasses: KnownSubclasses.Aux[Base, C],
toHList: ToHList.Aux[C, L],
fillWith: FillWith[nullPoly.type, L],
mapper: Mapper[toNamePoly.type, L],
): DefaultSymbolicLabelling.Aux[Base, mapper.Out] =
DefaultSymbolicLabelling.instance[Base, mapper.Out](mapper(fillWith()))