基于Shapeless的HList折叠实现Combiner编译问题排查与求解
a) 解决编译找不到LeftFolder.Aux的问题
你自己排查的结论完全正确——就是导入错了LeftFolder的版本!Shapeless里针对tuple和HList分别提供了LeftFolder实现,你不小心导入了tuple版本的,而你的Combiner是基于HList的,所以编译器找不到匹配的Aux实例。
只需要把导入改成HList对应的版本就行:
// 正确导入 import shapeless.ops.hlist.LeftFolder // 删掉错误的 import shapeless.ops.tuple.LeftFolder
举个修正后的完整代码片段参考:
import shapeless.{HList, HNil, Poly1} import shapeless.ops.hlist.LeftFolder trait Provider[T] { def provide: T } trait Combiner[Out, Providers <: HList] { def combine(providers: Providers): Out } object ShapelessCombiner { def shapelessCombiner[Out, Providers <: HList](zero: Out)(poly: Poly1 { type Case[A] = Out => Provider[A] => Out })(implicit folder: LeftFolder.Aux[Providers, Out, poly.type, Out]): Combiner[Out, Providers] = new Combiner[Out, Providers] { override def combine(providers: Providers): Out = providers.foldLeft(zero)(poly) } }
修正导入后,编译器就能正确识别HList的LeftFolder.Aux类型,编译错误就会消失。
b) 更优的实现方案
其实基于你的需求,有几种更简洁的方式可以实现,不一定需要手动定义Combiner trait或者写复杂的类型类推导:
方案1:简化Combiner的自动派生
可以让Combiner的实例通过implicit自动生成,这样调用的时候更简洁,不需要手动传入LeftFolder实例:
import shapeless._ import shapeless.ops.hlist.LeftFolder trait Provider[T] { def provide: T } trait Combiner[Out, Providers <: HList] { def combine(providers: Providers): Out } object Combiner { // 自动推导Combiner实例 implicit def makeCombiner[Out, Providers <: HList, P <: Poly1](zero: Out)(implicit folder: LeftFolder.Aux[Providers, Out, P, Out], poly: P { type Case[A] = Out => Provider[A] => Out } ): Combiner[Out, Providers] = new Combiner[Out, Providers] { override def combine(providers: Providers): Out = providers.foldLeft(zero)(poly) } // 辅助方法,方便获取实例 def apply[Out, Providers <: HList](zero: Out)(implicit c: Combiner[Out, Providers]): Combiner[Out, Providers] = c } // 示例:定义累加Provider[Int]的Poly object sumPoly extends Poly1 { implicit def handleInt: Case.Aux[(Int, Provider[Int]), Int] = at { case (acc, p) => acc + p.provide } } // 使用方式 val providers = new Provider[Int] { def provide = 5 } :: new Provider[Int] { def provide = 10 } :: HNil val sumCombiner = Combiner[Int, providers.type](0)(sumPoly) sumCombiner.combine(providers) // 输出15
方案2:跳过Combiner trait,直接封装工具方法
如果你的场景不需要Combiner作为抽象层,可以直接写一个工具方法,把foldLeft的逻辑封装起来,代码更精简:
import shapeless._ import shapeless.ops.hlist.LeftFolder trait Provider[T] { def provide: T } object ProviderMerger { def merge[Out, Providers <: HList](zero: Out, providers: Providers)(poly: Poly1 { type Case[A] = Out => Provider[A] => Out })(implicit folder: LeftFolder.Aux[Providers, Out, poly.type, Out]): Out = providers.foldLeft(zero)(poly) } // 使用示例 val result = ProviderMerger.merge(0, providers)(sumPoly) // 直接得到15
方案3:结合Cats(如果允许引入额外依赖)
如果你的项目已经用了Cats这类函数式库,可以利用它的Monoid类型类来简化合并逻辑,不需要手动写Poly:
import shapeless._ import cats.Monoid import cats.instances.int._ // 比如Int的Monoid实例 import shapeless.ops.hlist.Mapper trait Provider[T] { def provide: T } object ProviderMerger { // 把所有Provider的provide值映射出来,再用Monoid合并 def combineAll[Providers <: HList, Out](providers: Providers)(implicit mapper: Mapper.Aux[Provider ~> λ[t => Out], Providers, Out :: HList], monoid: Monoid[Out] ): Out = { val values = providers.map(λ[Provider ~> λ[t => Out]](_.provide.asInstanceOf[Out])) values.foldLeft(monoid.empty)(monoid.combine) } } // 使用示例 val total = ProviderMerger.combineAll[providers.type, Int](providers) // 输出15
这个方案的好处是可以复用Cats已经定义好的各种Monoid实例,比如字符串、集合、数字等,不需要为每种类型写Poly。
内容的提问来源于stack exchange,提问作者L.Lampart
相关产品推荐
相关产品推荐

