Shapeless - 如何为 Coproduct 派生 LabelledGeneric

问题描述 投票:0回答:1

我正在尝试为 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 typeclass implicit shapeless generic-derivation
1个回答
2
投票

对于非

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()))
© www.soinside.com 2019 - 2024. All rights reserved.