在 Slick+shapeless 中解压缩元组查询

Unzip a query of tuple in Slick+shapeless

我有一个 Slick(分组)查询返回一个整数元组,然后应该使用 SQL 聚合函数之一将其组合。元组的大小不是动态的,但我希望能够在一个地方定义它,以后可以更改它。我在项目的其他地方使用了 shapeless,所以这里也很自然地使用它。

所以,假设我们有一个元组类型 type Agg = (Rep[Int], Rep[Int], Rep[Int]) 和一个类型为 Query[Agg, _, Seq]

的查询

然后我可以像这样为聚合定义多边形:

object sum extends (Query[Rep[Int], Int, Seq] -> Rep[Int])(_.sum.getOrElse(0))
object sumAgg extends(Query[Agg, _, Seq] -> Agg)(q => (q.map(_._1), q.map(_._2), q.map(_._3)).map(sum))

但我找不到摆脱 sumAgg poly 中显式元组解压缩的方法。如何将 Int 元组查询(又名 Agg)转换为 Int 查询元组?

我们来简化一下。假设我有

val hlist = 1 :: 2 :: 3 :: HNil
val tuple = (4, 5, 6)
hlist.zipWithIndex.map(m)

要生成 (1*4) :: (2*5) :: (3*6) :: HNil 的 hlist,m 的定义是什么?我知道我可以直接用元组压缩 hlist 但在这种情况下我认为我确实需要根据它们的位置一个一个地选择元组元素。

正在回答您的简化版本:

import shapeless._
import syntax.std.tuple._  // brings implicits for tuple.productElements

// defines your polymorphic mapper

object MulPoly extends Poly1 {
  // you're going to need one case for each pair of types that you might face.
  // E.g. (Int, Int), (Int, String), (String, String), etc.
  implicit val intPairCase: Case.Aux[(Int, Int), Int] = at({ case (a, b) => a * b })
}

val hlist = 1 :: 2 :: 3 :: HNil
val tuple = (4, 5, 6)
val tupleHList = tuple.productElements
>>> tupleHList: Int :: Int :: Int :: shapeless.HNil = 4 :: 5 :: 6 :: HNil

hlist
    .zip(tupleHList)  // intermediate result here: (1, 4) :: (2, 5) :: (3, 6) :: HNil
    .map(MulPoly)     // map MulPoly over the HList
>>> res0: Int :: Int :: Int :: shapeless.HNil = 4 :: 10 :: 18 :: HNil

所以,从本质上讲,您的 m 是一个 Poly1,它可以映射到构成您的 hlist 和元组的类型的 2 元组。

您可能想咨询 Type Astronaut's Guide to Shapeless, Section 7 about ploymorphic mapping, and this 如何从任何元组中获取 hlist。

尝试将 sumAgg 替换为类型 类。

import shapeless.{::, HList, HNil, Nat, Poly1, Succ}
import shapeless.nat._
import shapeless.poly.->
import shapeless.syntax.std.tuple._
import shapeless.ops.hlist.Tupler
import shapeless.ops.tuple.{At, Length, Mapper}
import slick.lifted.{Query, Rep, Shape}
import slick.jdbc.PostgresProfile.api._

trait MkHList[Agg <: Product, N <: Nat] {
  type Out <: HList
  def apply(q: Query[Agg, _, Seq]): Out
}
object MkHList {
  type Aux[Agg <: Product, N <: Nat, Out0 <: HList] = MkHList[Agg, N] { type Out = Out0 }
  def instance[Agg <: Product, N <: Nat, Out0 <: HList](f: Query[Agg, _, Seq] => Out0): Aux[Agg, N, Out0] = new MkHList[Agg, N] {
    override type Out = Out0
    override def apply(q: Query[Agg, _, Seq]): Out = f(q)
  }

  implicit def zero[Agg <: Product]: Aux[Agg, _0, HNil] = instance(_ => HNil)
  implicit def succ[Agg <: Product, N <: Nat, A](implicit
    tailMkHList: MkHList[Agg, N],
    at: At.Aux[Agg, N, Rep[A]],
    shape: Shape[_ <: FlatShapeLevel, Rep[A], A, Rep[A]]
  ): Aux[Agg, Succ[N], Query[Rep[A], A, Seq] :: tailMkHList.Out] =
    instance(q => q.map(_.at[N]) :: tailMkHList(q))
}

trait SumAgg[Agg <: Product] {
  def apply(q: Query[Agg, _, Seq]): Agg
}
object SumAgg {
  implicit def mkSumAgg[Agg <: Product, N <: Nat, L <: HList, Tpl <: Product](implicit
    length: Length.Aux[Agg, N],
    mkHList: MkHList.Aux[Agg, N, L],
    tupler: Tupler.Aux[L, Tpl],
    mapper: Mapper.Aux[Tpl, sum.type, Agg]
  ): SumAgg[Agg] = q => tupler(mkHList(q)).map(sum)
}

def sumAgg[Agg <: Product](q: Query[Agg, _, Seq])(implicit sumAggInst: SumAgg[Agg]): Agg = sumAggInst(q)

type Agg = (Rep[Int], Rep[Int], Rep[Int])
sumAgg(??? : Query[Agg, _, Seq]): Agg