取 Seq[_] 的 HList 并用值的笛卡尔积生成 Seq[HList]

Taking HList of Seq[_] and generating Seq[HList] with cartesian product of values

我是 Shapless 的新手。

我正在尝试编写一个函数,它将采用 HList 不同类型的序列,将其转换为包含原始 HList 的笛卡尔积的 Seq[HList]' s 元素,并遍历生成的 Sequence

例如:

val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil

cartesianProduct: Seq[Boolean :: Int :: String :: HNil] = Seq(
  true :: 1 :: foo :: HNil, 
  true :: 1 :: bar :: HNil, 
  true :: 2 :: foo :: HNil, 
  true :: 2 :: bar :: HNil, 
  true :: 3 :: foo :: HNil, 
  true :: 3 :: bar :: HNil, 
  false :: 1 :: foo :: HNil, 
  false :: 1 :: bar :: HNil, 
  false :: 2 :: foo :: HNil, 
  false :: 2 :: bar :: HNil, 
  false :: 3 :: foo :: HNil, 
  false :: 3 :: bar :: HNil)

我能够使用以下代码在 Intellij Scala Worksheet 中实现此目的:

import shapeless._
import shapeless.ops.hlist.LeftFolder


object combine extends Poly {
  implicit def `case`[T <: HList, S] = use((acc : Seq[T], curr : Seq[S]) => {
    for {
      el <- curr
      v <- acc
    } yield el :: v
  })
}

val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil

val combinations = input.foldLeft(Seq[HNil](HNil))(combine) 

combinations.foreach(println)

这里一切正常,我想,因为编译器知道 input 的完整类型。

但是,当我尝试将整个操作包装在一个函数中时,input 的完整类型丢失了,我无法对 foldLeft 的结果调用 foreach:

def cartesian[T <: HList](input: T)
         (implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
  input.foldLeft(Seq[HNil](HNil))(combine)
    .foreach(println)
}

编译器抱怨:

  value foreach is not a member of folder.Out
    input.foldLeft(Seq[HNil](HNil))(combine).foreach(println)
                                            ^

我想有一些隐含的证据我可以请求断言 input 的正确形状(Seq[_]HList),从而让编译器找出结果类型foldLeft,但我不知道它可能是什么...

希望有人能帮我解决这个问题。 谢谢。

更新: 我对这个问题的最终目标是,给定 Seq[_]HList 来派生一个函数(可能在 class 的情况下),该函数将接受与输入 HList 具有相同参数的函数以及以相同顺序匹配 'Seq' 元素类型的参数类型。例如,对于上面的输入,函数将是 f: (Boolean, Int, String) => R 所以实际上我可以使用 f 迭代输入的笛卡尔积。 最终代码如下所示:

import shapeless._
import shapeless.ops.function.FnToProduct
import shapeless.ops.hlist.LeftFolder
import shapeless.syntax.std.function.fnHListOps


object combine extends Poly {
  implicit def `case`[EL <: HList, S] = use((acc : Seq[EL], curr : Seq[S]) => {
    for {
      el <- curr
      v <- acc
    } yield el :: v
  })
}

case class Cartesian[R <: HList, F, FR](combinations: Seq[R])
                                 (implicit ftp: FnToProduct.Aux[F, R => Unit]) {
  def foreach(f: F) =  combinations.foreach(f.toProduct)
}


def cartesian[T <: HList, R <: HList, F, FR](variants: T)(implicit
                             folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[R]],
                             fnToProd: FnToProduct.Aux[F, R => Unit]
                            ) = {
  val combinations: Seq[R] = variants.foldLeft(Seq[HNil](HNil))(combine)
  Cartesian(combinations)
}

val variants = Seq(true, false) :: Seq("foo", "bar") :: Seq(1, 2, 3) :: HNil

cartesian(variants).foreach((a, b, c) => println(s"$a, $b, $c")) 

请注意,函数参数 abc 的类型已正确推断为 BooleanStringInt。 目前传递给 foreach 的函数的结果类型必须被修复(在上面的代码中它是 Unit)。无法从传入的函数中推断出来。

问题不是编译器对输入一无所知,而是它对输出.

一无所知

def cartesian 内部,编译器只知道在 foldLeft 之后你得到某种类型 folder.Out (这取决于 shapeless 会为你计算的实例)。

为了确保结果类型,您可以使用 LeftFolder.Aux 和一个额外的类型参数,例如

def cartesian[T <: HList](input: T)
         (implicit folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[Any]]) = {
  input.foldLeft(Seq[HNil](HNil))(combine)
    .foreach(println)
}

现在编译器将知道结果是 Seq[Any] 的某个子类型,因此可以对其调用 foreach


当然,这只是里面的问题def。在呼叫站点,输出类型将根据输入进行解析,因此您可以在没有 Aux:

的情况下执行此操作
def cartesian2[T <: HList](input: T)
         (implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
  input.foldLeft(Seq[HNil](HNil))(combine)
}

cartesian2(input).foreach(println)

可运行代码:https://scalafiddle.io/sf/n409yNW/2