【问题标题】:Shapeless - How to derive LabelledGeneric for CoproductShapeless - 如何为 Coproduct 派生 LabelledGeneric
【发布时间】:2023-02-24 02:52:10
【问题描述】:

我正在尝试为 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))
}

有什么提示吗?通过无形宏查看,但到目前为止我没有发现任何有用的东西......

米。

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    对于 not sealed trait,在 Shapeless 中定义的 Generic/LabelledGeneric 的实例不能工作。

    所有此类宏都使用.knownDirectSubclasses。它仅适用于密封特征。

    Scala reflection: knownDirectSubclasses only works for sealed traits?

    对于未密封的特征,我总是可以在不同的文件 (case class Case4() extends Base) 甚至在运行时 (toolbox.define(q"case class Case4() extends Base")) 添加 Base 的继承者。

    如果您只对当前文件中定义的继承者感兴趣,那么也许您可以避免使用 .knownDirectSubclasses 并编写一个遍历当前文件的 AST 并寻找继承者的宏。


    到目前为止,我找不到从C 派生Ll 的方法。

    不难

    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]()
    
        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
    //
    //  val traverser = new Traverser {
    //    override def traverse(tree: Tree): Unit = {
    //      tree match {
    //        case q"$_ class $_[..$_] $_(...$_) extends { ..$_ } with ..$parents { $_ => ..$_ }"
    //          if parents.exists(p => A =:= getType(p)) =>
    //            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
    

    所以你可以推导GenericDefaultSymbolicLabelling(以及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()))
    

    【讨论】:

    • > 对于 Shapeless 中定义的 Generic/LabelledGeneric 的非密封特征实例无法工作。我想你的意思是utilities to automatically generate Generic/LabelledGeneric。但是,如果有人设法以某种方式定义必要的隐式,那么一切似乎都可以正常工作——就像我的例子一样。
    • @user3663733 查看类型类 KnownSubclasses 的另一个更新
    • 就像一个魅力,谢谢。
    猜你喜欢
    • 1970-01-01
    • 2018-10-25
    • 1970-01-01
    • 2019-07-25
    • 1970-01-01
    • 2018-09-01
    • 2016-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多