scala 无形状-如何为联产品派生LabelledGeneric

ix0qys7i  于 2023-03-02  发布在  Scala
关注(0)|答案(1)|浏览(148)

我正在尝试为Coproduct生成LabelledGeneric,这样它就可以用来代替典型的sealed trait层次结构。到目前为止,我能够通过显式指定DefaultSymbolicLabelling的标签来实现这一点,但我觉得应该可以从coproduct的类型成员自动派生它。

/**
 * 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))
}

有什么线索吗?看了看无形的宏,但到目前为止我没发现什么有用的...
米。

ecfsfe2w

ecfsfe2w1#

对于一个非sealed的trait,在Shapeless中定义的Generic/LabelledGeneric的示例不能工作。
所有这样的宏都使用.knownDirectSubclasses,它只对密封的trait有效。
Scala reflection: knownDirectSubclasses only works for sealed traits?
对于一个未密封的trait,我总是可以在不同的文件(case class Case4() extends Base)中添加Base的继承者,甚至在运行时(toolbox.define(q"case class Case4() extends 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
一个三个三个一个
(This实现不适用于泛型特征和类。)
因此,可以按如下所示推导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()))

相关问题