scala - Shapeless3 和注释

标签 scala typeclass implicit shapeless scala-3

这是 Shapeless and annotations 的后续内容。最初的问题是在 Scala 2 和 Shapeless2 的背景下提出的。 Shapeless2 的一些功能已迁移到 Shapeless3,例如注释。问题是,如何将解决方案迁移到Shapeless3?尤其是 Poly2 周围的代码?

以下是要迁移到 Shapeless3 的解决方案的复制/粘贴:

import shapeless.ops.hlist.{RightFolder, Zip}
import shapeless.{::, Annotations, Generic, HList, HNil, Lazy, Poly2}
import scala.annotation.StaticAnnotation

object App {
  case class MyAnnotation(func: String) extends StaticAnnotation

  object Collector extends Poly2 {
//    implicit def myCase[ACC <: HList, E] = at[(E, Option[PII]), ACC] {
//      case ((e, None), acc) => e :: acc
//      case ((e, Some(MyAnnotation(func))), acc) => {
//        println(func)
//        e :: acc
//      }
//    }

    implicit def someCase[ACC <: HList, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E :: ACC] = at {
      case ((e, Some(MyAnnotation(func))), acc) =>
        println(func)
        e :: acc
    }

    implicit def noneCase[ACC <: HList, E]: Case.Aux[(E, None.type), ACC, E :: ACC] = at {
      case ((e, None), acc) => e :: acc
    }
  }

  trait Modifier[T] {
    def modify(t: T): T
  }

  implicit def hListModifier[HL <: HList]: Modifier[HL] = identity(_) 
  // added as an example, you should replace this with your Modifier for HList

  implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](implicit
    gen: Generic.Aux[T, HL],
    ser: Lazy[Modifier[HL]],
    annots: Annotations.Aux[MyAnnotation, T, AL],
    zip: Zip.Aux[HL :: AL :: HNil, ZL],
    rightFolder: RightFolder.Aux[ZL, HNil/*.type*/, Collector.type, HL /*added*/]
    ): Modifier[T] = new Modifier[T] {
    override def modify(t: T): T = {
      val generic = gen.to(t)
      println(generic)
      val annotations = annots()
      println(annotations)
      val zipped = zip(generic :: annotations :: HNil)
      println(zipped)
      val modified = zipped.foldRight(HNil : HNil /*added*/)(Collector)
      println(modified)

      val typed = gen.from(modified)
      typed
    }
  }

  case class Test(a: String, @MyAnnotation("sha1") b: String)

  val test = Test("A", "B")
  val modifier: Modifier[Test] = implicitly[Modifier[Test]]

  def main(args: Array[String]): Unit = {
    val test1 = modifier.modify(test) // prints "sha1"
    println(test1) // Test(A,B)
  }
}

最佳答案

在 Scala 3 中,Tuple 用于 HListMirror 用于 Generic/LabeledGeneric.有polymorphic functions但它们是参数多态性多态性,而不是像Poly那样的临时多态性多态性。

Shapeless 3具有注释Typeable和派生工具(包装Mirror)。

实现缺失的部分并不难(GenericCoproductPoly、类型类等)

Scala 3 collection partitioning with subtypes

import shapeless3.deriving.Annotations
import scala.deriving.Mirror
import scala.util.NotGiven
import scala.annotation.StaticAnnotation

//================= GENERIC ====================
trait Generic[T] {
  type Repr
  def to(t: T): Repr
  def from(r: Repr): T
}

object Generic {
  type Aux[T, Repr0] = Generic[T] {type Repr = Repr0}

  def instance[T, Repr0](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] =
    new Generic[T] {
      override type Repr = Repr0
      override def to(t: T): Repr0 = f(t)
      override def from(r: Repr0): T = g(r)
    }

  object ops {
    extension[A] (a: A) {
      def toRepr(using g: Generic[A]): g.Repr = g.to(a)
    }

    extension[Repr] (a: Repr) {
      def to[A](using g: Generic.Aux[A, Repr]): A = g.from(a)
    }
  }

  given [T <: Product](using
    // ev: NotGiven[T <:< Tuple],
    // ev1: NotGiven[T <:< Coproduct],
    m: Mirror.ProductOf[T],
    m1: Mirror.ProductOf[m.MirroredElemTypes]
  ): Aux[T, m.MirroredElemTypes] = instance(
    m1.fromProduct(_),
    m.fromProduct(_)
  )

//  given[T, C <: Coproduct](using
//    // ev: NotGiven[T <:< Tuple],
//    // ev1: NotGiven[T <:< Coproduct],
//    m: Mirror.SumOf[T],
//    ev2: Coproduct.ToCoproduct[m.MirroredElemTypes] =:= C
//  ): Generic.Aux[T, C/*Coproduct.ToCoproduct[m.MirroredElemTypes]*/] = {
//    instance(
//      t => Coproduct.unsafeToCoproduct(m.ordinal(t), t).asInstanceOf[C],
//      Coproduct.unsafeFromCoproduct(_).asInstanceOf[T]
//    )
//  }
}


//================= COPRODUCT ====================
//sealed trait Coproduct extends Product with Serializable
//sealed trait +:[+H, +T <: Coproduct] extends Coproduct
//final case class Inl[+H, +T <: Coproduct](head: H) extends (H +: T)
//final case class Inr[+H, +T <: Coproduct](tail: T) extends (H +: T)
//sealed trait CNil extends Coproduct
//
//object Coproduct {
//  def unsafeToCoproduct(length: Int, value: Any): Coproduct =
//    (0 until length).foldLeft[Coproduct](Inl(value))((c, _) => Inr(c))
//
//  @scala.annotation.tailrec
//  def unsafeFromCoproduct(c: Coproduct): Any = c match {
//    case Inl(h) => h
//    case Inr(c) => unsafeFromCoproduct(c)
//    case _: CNil => sys.error("impossible")
//  }
//
//  type ToCoproduct[T <: Tuple] <: Coproduct = T match {
//    case EmptyTuple => CNil
//    case h *: t => h +: ToCoproduct[t]
//  }
//
//  type ToTuple[C <: Coproduct] <: Tuple = C match {
//    case CNil => EmptyTuple
//    case h +: t => h *: ToTuple[t]
//  }
//}


//================= POLY ====================
trait Cases {
  type Case1[Fn, A] = poly.Case[Fn, A *: EmptyTuple]
  object Case1 {
    type Aux[Fn, A, Result] = poly.Case.Aux[Fn, A *: EmptyTuple, Result]
    def apply[Fn, A, Result](fn: A => Result): Case1.Aux[Fn, A, Result] =
      poly.Case { case a *: EmptyTuple => fn(a) }
  }

  type Case2[Fn, A, B] = poly.Case[Fn, A *: B *: EmptyTuple]
  object Case2 {
    type Aux[Fn, A, B, Result] = poly.Case.Aux[Fn, A *: B *: EmptyTuple, Result]
    def apply[Fn, A, B, Result](fn: (A, B) => Result): Case2.Aux[Fn, A, B, Result] =
      poly.Case { case a *: b *: EmptyTuple => fn(a, b) }
  }
}

trait CaseInst {
  given inst1[Fn <: Poly, A, Res]: Conversion[poly.Case.Aux[Fn, A *: EmptyTuple, Res], A => Res] =
    cse => a => cse.value(a *: EmptyTuple)
  given inst2[Fn <: Poly, A, B, Res]: Conversion[poly.Case.Aux[Fn, A *: B *: EmptyTuple, Res], (A, B) => Res] =
    cse => (a, b) => cse.value(a *: b *: EmptyTuple)
}

object poly extends Cases {
  trait Case[P, L <: Tuple] {
    type Result
    val value: L => Result

    def apply(t: L): Result = value(t)
    def apply()(using ev: EmptyTuple =:= L): Result = value(EmptyTuple)
    def apply[T](t: T)(using ev: (T *: EmptyTuple) =:= L): Result = value(t *: EmptyTuple)
    def apply[T, U](t: T, u: U)(using ev: (T *: U *: EmptyTuple) =:= L): Result = value(t *: u *: EmptyTuple)
  }

  object Case extends CaseInst {
    type Aux[P, L <: Tuple, Result0] = Case[P, L] {type Result = Result0}
    def apply[P, L <: Tuple, R](v: L => R): Aux[P, L, R] = new Case[P, L] {
      type Result = R
      val value = v
    }
  }
}

trait PolyApply {
  type λ <: Singleton
  def apply[A](a: A)(using cse: poly.Case[λ, A *: EmptyTuple]): cse.Result = cse(a *: EmptyTuple)
  def apply[A, B](a: A, b: B)(using cse: poly.Case[λ, A *: B *: EmptyTuple]): cse.Result = cse(a *: b *: EmptyTuple)
}

trait Poly extends PolyApply {
  type λ = this.type

  type ProductCase[L <: Tuple] = poly.Case[this.type, L]
  object ProductCase extends Serializable {
    type Aux[L <: Tuple, Result0] = ProductCase[L] {type Result = Result0}
    def apply[L <: Tuple, R](v: L => R) = new ProductCase[L] {
      type Result = R
      val value = v
    }
  }

  def apply[R](using c: ProductCase.Aux[EmptyTuple, R]): R = c()
}

trait PolyInst {
  implicit def inst0(p: Poly)(implicit cse: p.ProductCase[EmptyTuple]): cse.Result = cse()
  implicit def inst1[A](fn: Poly)(implicit cse: fn.ProductCase[A *: EmptyTuple]): A => cse.Result =
    a => cse(a *: EmptyTuple)
  implicit def inst2[A, B](fn: Poly)(implicit cse: fn.ProductCase[A *: B *: EmptyTuple]): (A, B) => cse.Result =
    (a, b) => cse(a *: b *: EmptyTuple)
}

object Poly extends PolyInst

trait Poly0 extends Poly {
  type Case0[T] = ProductCase.Aux[EmptyTuple, T]
  def at[T](t: T) = new ProductCase[EmptyTuple] {
    type Result = T
    val value = _ => t
  }
}

trait Poly1 extends Poly { self =>
  type Case[A] = poly.Case[self.type, A *: EmptyTuple]

  object Case {
    type Aux[A, Result0] = poly.Case.Aux[self.type, A *: EmptyTuple, Result0]
  }

  class CaseBuilder1[A] {
    def apply[Res](fn: A => Res): Case.Aux[A, Res] = poly.Case { case a *: EmptyTuple => fn(a) }
  }

  def at[A]: CaseBuilder1[A] = new CaseBuilder1[A]
}


trait Poly2 extends Poly { self =>
  type Case[A, B] = poly.Case[self.type, A *: B *: EmptyTuple]

  object Case {
    type Aux[A, B, Result0] = poly.Case.Aux[self.type, A *: B *: EmptyTuple, Result0]
  }

  class CaseBuilder2[A, B] {
    def apply[Res](fn: (A, B) => Res): Case.Aux[A, B, Res] = poly.Case { case a *: b *: EmptyTuple => fn(a, b) }
  }

  def at[A, B]: CaseBuilder2[A, B] = new CaseBuilder2[A, B]
}


//================= TYPE CLASSES ====================
trait DepFn0 {
  type Out
  def apply(): Out
}

trait DepFn1[T] {
  type Out
  def apply(t: T): Out
}

trait DepFn2[T, U] {
  type Out
  def apply(t: T, u: U): Out
}

trait ConstMapper[C, L <: Tuple] extends DepFn2[C, L]  {
  type Out <: Tuple
}
object ConstMapper {
  def apply[C, L <: Tuple](using mapper: ConstMapper[C, L]): Aux[C, L, mapper.Out] = mapper
  type Aux[C, L <: Tuple, Out0 <: Tuple] = ConstMapper[C, L] {type Out = Out0}

  given hnilConstMapper[C]: Aux[C, EmptyTuple, EmptyTuple] =
    new ConstMapper[C, EmptyTuple] {
      type Out = EmptyTuple
      def apply(c: C, l: EmptyTuple): Out = l
    }

  given hlistConstMapper[H, T <: Tuple, C, OutT <: Tuple]
  (using mct: ConstMapper.Aux[C, T, OutT]): Aux[C, H *: T, C *: OutT] =
    new ConstMapper[C, H *: T] {
      type Out = C *: OutT
      def apply(c: C, l: H *: T): Out = c *: mct(c, l.tail)
    }
}

trait ZipOne[H <: Tuple, T <: Tuple] extends DepFn2[H, T] {
  type Out <: Tuple
}

object ZipOne extends LowPriorityZipOne {
  given zipOne0: Aux[EmptyTuple, EmptyTuple, EmptyTuple] =
    new ZipOne[EmptyTuple, EmptyTuple] {
      type Out = EmptyTuple
      def apply(h: EmptyTuple, t: EmptyTuple): Out = EmptyTuple
    }

  given zipOne3[H, T <: Tuple]: Aux[H *: EmptyTuple, T *: EmptyTuple, (H *: T) *: EmptyTuple] =
    new ZipOne[H *: EmptyTuple, T *: EmptyTuple] {
      type Out = (H *: T) *: EmptyTuple
      def apply(h: H *: EmptyTuple, t: T *: EmptyTuple): Out = (h.head *: t.head) *: EmptyTuple
    }
}

trait LowPriorityZipOne {
  def apply[H <: Tuple, T <: Tuple](using zip: ZipOne[H, T]): Aux[H, T, zip.Out] = zip
  type Aux[H <: Tuple, T <: Tuple, Out0 <: Tuple] = ZipOne[H, T] {type Out = Out0}

  given zipOne1[H <: Tuple]: Aux[H, EmptyTuple, EmptyTuple] =
    new ZipOne[H, EmptyTuple] {
      type Out = EmptyTuple
      def apply(h: H, t: EmptyTuple): Out = EmptyTuple
    }

  given zipOne2[T <: Tuple]: Aux[EmptyTuple, T, EmptyTuple] =
    new ZipOne[EmptyTuple, T] {
      type Out = EmptyTuple
      def apply(h: EmptyTuple, t: T): Out = EmptyTuple
    }

  given zipOne4[HH, HT <: Tuple, TH <: Tuple, TT <: Tuple, ZotOut <: Tuple]
  (using zot: ZipOne.Aux[HT, TT, ZotOut], ev: Tuple.Head[TH *: TT] =:= TH /*???*/): Aux[HH *: HT, TH *: TT, (HH *: TH) *: ZotOut] =
    new ZipOne[HH *: HT, TH *: TT] {
      type Out = (HH *: TH) *: ZotOut
      def apply(h: HH *: HT, t: TH *: TT): Out = (h.head *: ev(t.head)) *: zot(h.tail, t.tail)
    }
}

trait Transposer[L <: Tuple] extends DepFn1[L] {
  type Out <: Tuple
}

object Transposer {
  def apply[L <: Tuple](using transposer: Transposer[L]): Aux[L, transposer.Out] = transposer
  type Aux[L <: Tuple, Out0 <: Tuple] = Transposer[L] {type Out = Out0}

  given hnilTransposer: Aux[EmptyTuple, EmptyTuple] =
    new Transposer[EmptyTuple] {
      type Out = EmptyTuple
      def apply(l: EmptyTuple): Out = l
    }

  given hlistTransposer1[H <: Tuple, MC <: Tuple, Out0 <: Tuple]
  (using mc: ConstMapper.Aux[EmptyTuple, H, MC], zo: ZipOne.Aux[H, MC, Out0]): Aux[H *: EmptyTuple, Out0] =
    new Transposer[H *: EmptyTuple] {
      type Out = Out0
      def apply(l: H *: EmptyTuple): Out = zo(l.head, mc(EmptyTuple, l.head))
    }

  given hlistTransposer2[H <: Tuple, TH <: Tuple, TT <: Tuple, OutT <: Tuple, Out0 <: Tuple]
  (using tt: Aux[TH *: TT, OutT], zo: ZipOne.Aux[H, OutT, Out0]): Aux[H *: TH *: TT, Out0] =
    new Transposer[H *: TH *: TT] {
      type Out = Out0
      def apply(l: H *: TH *: TT): Out = zo(l.head, tt(l.tail))
    }
}

trait Zip[L <: Tuple] extends DepFn1[L] {
  type Out <: Tuple
}

object Zip {
  def apply[L <: Tuple](using zip: Zip[L]): Aux[L, zip.Out] = zip
  type Aux[L <: Tuple, Out0 <: Tuple] = Zip[L] {type Out = Out0}

  given zipper[L <: Tuple, OutT <: Tuple]
  (using
   transposer: Transposer.Aux[L, OutT]
  ): Aux[L, OutT] =
    new Zip[L] {
      type Out = OutT
      def apply(l: L): Out = l.transpose
    }
}

extension [L <: Tuple](l: L) {
  def transpose(using transpose: Transposer[L]): transpose.Out = transpose(l)
  def foldRight[R](z : R)(op : Poly)(using folder: RightFolder[L, R, op.type]): folder.Out = folder(l, z)
}

trait RightFolder[L <: Tuple, In, HF] extends DepFn2[L, In]
object RightFolder {
  def apply[L <: Tuple, In, F](using folder: RightFolder[L, In, F]): Aux[L, In, F, folder.Out] = folder
  type Aux[L <: Tuple, In, HF, Out0] = RightFolder[L, In, HF] {type Out = Out0}

  given hnilRightFolder[In, HF]: Aux[EmptyTuple, In, HF, In] =
    new RightFolder[EmptyTuple, In, HF] {
      type Out = In
      def apply(l: EmptyTuple, in: In): Out = in
    }

  given hlistRightFolder[H, T <: Tuple, In, HF, OutT]
  (using ft: RightFolder.Aux[T, In, HF, OutT], f: poly.Case2[HF, H, OutT]): Aux[H *: T, In, HF, f.Result] =
    new RightFolder[H *: T, In, HF] {
      type Out = f.Result
      def apply(l: H *: T, in: In): Out = f(l.head, ft(l.tail, in))
    }
}


//================= YOUR SETTING ====================
case class MyAnnotation(func: String) extends StaticAnnotation

object Collector extends Poly2 {
  given [ACC <: Tuple, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E *: ACC] = at {
    case ((e, Some(MyAnnotation(func))), acc) =>
      println(func)
      e *: acc
  }

  given [ACC <: Tuple, E]: Case.Aux[(E, None.type), ACC, E *: ACC] = at {
    case ((e, None), acc) => e *: acc
  }
}

trait Modifier[T] {
  def modify(t: T): T
}

given hListModifier[HL <: Tuple]: Modifier[HL] = identity(_)
// added as an example, you should replace this with your Modifier for HList

given genericModifier[T, HL <: Tuple, AL <: Tuple, ZL <: Tuple](using
  gen: Generic.Aux[T, HL],
  ser: /*Lazy[*/Modifier[HL]/*]*/,
  annots: Annotations.Aux[MyAnnotation, T, AL],
  zip: Zip.Aux[HL *: AL *: EmptyTuple, ZL],
  rightFolder: RightFolder.Aux[ZL, EmptyTuple, Collector.type, HL]
): Modifier[T] = new Modifier[T] {
  override def modify(t: T): T = {
    val generic = gen.to(t)
    println(generic)
    val annotations = annots()
    println(annotations)
    val zipped = zip(generic *: annotations *: EmptyTuple)
    println(zipped)
    val modified = zipped.foldRight(EmptyTuple)(Collector)
    println(modified)

    val typed = gen.from(modified)
    typed
  }
}

case class Test(a: String, @MyAnnotation("sha1") b: String)

val test = Test("A", "B")
val modifier: Modifier[Test] = summon[Modifier[Test]]

@main def run = {
  val test1 = modifier.modify(test) // prints "sha1"
  println(test1) // Test(A,B)
}

也许某些类型类可以替换为 match typescompile-time计算。

实现Lazy可能很棘手。目前还不清楚是否需要。有一些隐式名称,但它们并不等同于 Lazy ( 1 2 )。原则上,Lazy 可以在 Scala 3 中实现,因为 Scala 3 中隐式的编译器内部结构与 Scala 2 中的类似 ( 1 2 3 )。


Shapeless and annotations

I would like to have some function applied to fields in a case class, that are annotated with MyAnnotation. The idea is to transform type T into its generic representation, extract annotations, zip, fold right (or left) to reconstruct a generic representation and finally get back to type T.

这是 Scala 3 的更简单的解决方案

import shapeless3.deriving.Annotations
import scala.annotation.StaticAnnotation
import scala.deriving.Mirror

case class MyAnnotation(func: String) extends StaticAnnotation

case class Test(a: String, @MyAnnotation("sha1") b: String)

def fold[Tup <: Tuple, Z, F[_, _]](tup: Tup, z: Z, f: [A, B] => (A, B) => F[A, B]): Tuple.Fold[Tup, Z, F] = tup match {
  case _: EmptyTuple => z
  case tup: (h *: t) => f[h, Tuple.Fold[t, Z, F]](tup.head, fold[t, Z, F](tup.tail, z, f))
}

type Collector[A, B <: Tuple] = A match {
  case (a, Some[MyAnnotation]) => a *: B
  case (a, None.type) => a *: B
}

transparent inline def foo[T <: Product](t: T)(using
  m: Mirror.ProductOf[T],
  m1: Mirror.ProductOf[m.MirroredElemTypes] {type MirroredElemTypes = m.MirroredElemTypes},
  ann: Annotations[MyAnnotation, T]
): Any = {
  val tuple: m.MirroredElemTypes = m1.fromProduct(t)
  println(s"tuple=$tuple")
  val annotations: ann.Out = ann()
  println(s"annotations=$annotations")
  type Zipped = Tuple.Zip[m.MirroredElemTypes, ann.Out]
  val zipped: Zipped = tuple.zip(annotations)
  println(s"zipped=$zipped")
  def collector[A, B <: Tuple](x: A, y: B): Collector[A, B] = (x match {
    case (a, Some(annot)) =>
      println(s"annot=$annot")
      a *: y
    case (a, None) =>
      a *: y
  }).asInstanceOf[Collector[A, B]]
  type Folded = Tuple.Fold[Zipped, EmptyTuple, [a, b] =>> Collector[a, b & Tuple]]
  val folded: Folded = fold[Zipped, EmptyTuple, [a, b] =>> Collector[a, b & Tuple]](
    zipped,
    EmptyTuple,
    [a, b] => (x: a, y: b) => collector(x, y.asInstanceOf[b & Tuple])
  )
  m.fromProduct(folded.asInstanceOf[Folded & Product])
}

val res: Test = foo(Test("aa", "bb")) // Test(aa,bb)
// tuple=(aa,bb)
// annotations=(None,Some(MyAnnotation(sha1)))
// zipped=((aa,None),(bb,Some(MyAnnotation(sha1))))
// annot=MyAnnotation(sha1)

关于scala - Shapeless3 和注释,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74355212/

相关文章:

scala - 运行 stanford LLDA tmt scala 模型时出现线程错误

haskell - Haskell 中的模糊实例解析

haskell - 自己的数据类型 - 它们与 native 数据类型之间的转换

haskell - 使用量化约束导出 Ord (forall a. Ord a => Ord (f a))

scala - 没有类型的隐式参数

scala - 在 Traits 中初始化值

scala - 我们可以使用单例 .type 作为类型参数吗?

scala - 用户包含 scalamacros paradise 的最简单方法

arrays - scala 中令人难以置信的隐式数组转换

scala - Scala 中带有隐式参数的函数类型