Shapeless – 如何为Coproduct派生LabelledGeneric

huangapple go评论53阅读模式
英文:

Shapeless - How to derive LabelledGeneric for Coproduct

问题

/**
 * 到目前为止,我无法从 `C` 中派生出 `L` 和 `l`。
 */
object ShapelessLabelledGenericForCoproduct extends App {
  trait Base // 没有 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))
}
英文:

I'm trying to generate LabelledGeneric for Coproduct, so that it can be used instead of typical sealed trait hierarchy. So far I was able to do it by explicit specification of labels for DefaultSymbolicLabelling, but I feel it should be possible to derive it automatically from coproduct's type members.

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

See code below with sealed trait; in general I'd like to achieve similar behavior, just without sealing the trait.

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

Any hints? Looked through shapeless macros, but so far I found nothing useful...

m.

答案1

得分: 2

以下是代码部分的翻译:

  1. 对于非sealed特质,在Shapeless中定义的Generic/LabelledGeneric实例无法工作。

  2. 所有这些宏都使用了.knownDirectSubclasses,这只适用于一个sealed特质。

  3. 对于非sealed特质,我可以在不同的文件中始终添加Base的继承者(例如:case class Case4() extends Base),甚至可以在运行时添加(例如:toolbox.define(q"case class Case4() extends Base"))。

  4. 如果您只关心当前文件中定义的继承者,那么也许您可以避免使用.knownDirectSubclasses,而是编写一个遍历当前文件AST并查找继承者的宏。

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

  6. 这不难。

  7. 这是一个示例代码,其中引入了ToName类型类。

  8. 所以,您可以按如下方式派生DefaultSymbolicLabelling

  9. 下面是具有遍历功能的代码。我引入了名为KnownSubclasses的类型类。

  10. 这个实现不适用于通用的特质和类。

  11. 所以,您可以按如下方式派生GenericDefaultSymbolicLabelling(因此也可以派生LabelledGeneric)。

请注意,上述内容是代码的翻译部分,仅包含代码相关信息。如果您需要进一步的信息或有其他问题,请告诉我。

英文:

For a not sealed trait, the instances of Generic/LabelledGeneric defined in Shapeless can't work.

All such macros are using .knownDirectSubclasses. It works only for a sealed trait.

https://stackoverflow.com/questions/34072300/scala-reflection-knowndirectsubclasses-only-works-for-sealed-traits

For a not sealed trait I can always add inheritors of Base in a different file (case class Case4() extends Base) or even at runtime (toolbox.define(q"case class Case4() extends Base")).

If you're interested only in inheritors defined in the current file then maybe you can avoid usage of .knownDirectSubclasses and write a macro traversing the AST of current file and looking for the inheritors.


> So far I found no way to derive L and l from C.

It's not hard

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

So you can derive DefaultSymbolicLabelling as follows

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()))

Here is the code with traversing. I'm introducing type class 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 = {
//    val withoutArgs = t match {
//      case q"${t1@tq"$_[..$_]"}(...$_)" => t1
//      case _ => t
//    }
//    c.typecheck(tq"$withoutArgs", 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

(This implementation doesn't work for generic trait and classes.)

So you can derive Generic and DefaultSymbolicLabelling (and therefore LabelledGeneric) as follows

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()))

huangapple
  • 本文由 发表于 2023年2月23日 20:15:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75544693.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定