Shapeless入门:如何将Seq构成的HList转换为其笛卡尔积的Seq[HList]
嘿,刚上手Shapeless确实得适应一下它的类型驱动风格,不过你要的这个“Seq组成的HList转笛卡尔积Seq[HList]”的需求,用Shapeless的类型类来实现刚好合适,咱们一步步来写:
第一步:引入Shapeless依赖
首先得确保你的项目里加了Shapeless的依赖,比如sbt项目里:
libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
第二步:定义笛卡尔积的类型类
Shapeless的核心是用类型类来处理不同的HList类型,我们先定义一个CartesianProduct类型类,负责把输入的HList(每个元素是Seq)转换成对应的笛卡尔积Seq[HList]:
import shapeless._ trait CartesianProduct[L <: HList] { // 定义输出HList的类型 type Out <: HList // 实现转换逻辑 def apply(l: L): Seq[Out] } object CartesianProduct { // 用Aux类型别名简化类型参数的传递,让implicit实例的类型更清晰 type Aux[L <: HList, O <: HList] = CartesianProduct[L] { type Out = O } // 处理空HNil的情况:空的笛卡尔积就是包含HNil的Seq implicit val hnilCartesian: Aux[HNil, HNil] = new CartesianProduct[HNil] { type Out = HNil def apply(l: HNil): Seq[HNil] = Seq(HNil) } // 处理非空的Cons类型(即Seq[B] :: Tail) // 递归地把第一个Seq的每个元素,和Tail部分的笛卡尔积结果拼接 implicit def consCartesian[B, Tail <: HList, TailOut <: HList]( implicit tailCp: Aux[Tail, TailOut] ): Aux[Seq[B] :: Tail, B :: TailOut] = new CartesianProduct[Seq[B] :: Tail] { type Out = B :: TailOut def apply(l: Seq[B] :: Tail): Seq[Out] = l.head.flatMap(b => tailCp(l.tail).map(b :: _)) } }
第三步:封装调用函数
为了方便调用,我们写一个顶层函数,让Scala的implicit机制自动帮我们找到对应的类型类实例:
def cartesianProduct[L <: HList](l: L)(implicit cp: CartesianProduct[L]): Seq[cp.Out] = cp(l)
第四步:示例使用与遍历
现在就可以用你给的输入来测试了,还可以把HList转成Tuple方便打印:
object CartesianProductExample extends App { // 你的输入HList val input = Seq(true, false) :: Seq(1,2,3) :: Seq("foo", "bar") :: HNil // 生成笛卡尔积 val productSeq = cartesianProduct(input) // 遍历并打印结果 println("生成的笛卡尔积:") productSeq.foreach { hlist => // 用Shapeless的tupled方法把HList转成普通Tuple import shapeless.syntax.std.tuple._ println(hlist.tupled) } }
运行这个程序,你会得到类似这样的输出:
生成的笛卡尔积: (true,1,foo) (true,1,bar) (true,2,foo) (true,2,bar) (true,3,foo) (true,3,bar) (false,1,foo) (false,1,bar) (false,2,foo) (false,2,bar) (false,3,foo) (false,3,bar)
简单解释一下逻辑
- 类型类
CartesianProduct是核心,它为不同的HList类型定义了转换规则:- 当输入是
HNil时,返回只包含HNil的Seq,这是递归的终止条件; - 当输入是
Seq[B] :: Tail时,先递归处理Tail得到它的笛卡尔积,然后把Seq[B]里的每个元素和Tail的每个结果拼接,用flatMap和map组合实现。
- 当输入是
Aux类型别名是Shapeless里的常用技巧,用来把类型类的关联类型(这里的Out)暴露成显式的类型参数,方便implicit实例的定义和使用。
内容的提问来源于stack exchange,提问作者dkarlinsky
相关产品推荐
相关产品推荐

