如何使用Shapeless将值与类型进行Zip操作?
问题与解决方案
问题背景
我有一个包含Coproduct的HList具体值:
val myhlist: (A :+: B :+: CNil) :: (Foo :+: Bar :+: CNil) :: HNil = ???
还有一个仅存在于类型层面的HList,无具体实例:
(A :+: CNil) :: (Bar :+: CNil) :: HNil
希望通过如下函数,将前者的每个Coproduct实例转换为后者对应位置的Coproduct类型:
def zipFn[A <: Coproduct, B <: Coproduct](a: A)(implicit basis: Basis[A, B]) = { a.deembed[B] }
当前尝试用Shapeless的柯里化多态函数,但因无法先绑定目标类型B再接收源实例A,写法无法满足需求:
object myZipFn extends Poly1 { implicit def default[A <: Coproduct, B <: Coproduct](implicit basis: Basis[A, B]) = at[A] { a => a.deembed[B] } } val curried = Poly.curried(myZipFn)
实现思路
要完成“以类型层面HList为模板,转换值层面HList”的需求,核心是让类型系统为HList的每一对对应元素推导Basis[A, B],可以通过以下两种方式实现:
方式1:用Poly2 + ZipWith(借助占位实例)
定义一个Poly2函数,接收目标类型的占位实例(仅用于类型推导)和源Coproduct实例,利用隐式Basis完成转换:
import shapeless._ import shapeless.coproduct.Basis import shapeless.ops.hlist.ZipWith // 定义多态转换函数:第二个参数是源Coproduct,第一个参数是目标类型的占位(仅用于类型绑定) object deembedPoly extends Poly2 { implicit def convert[A <: Coproduct, B <: Coproduct](implicit basis: Basis[A, B]) = at[B, A]((_, source) => source.deembed[B]) } // 创建目标类型HList的占位实例(仅用于类型推导,不需要实际值) val targetTypePlaceholder: (A :+: CNil) :: (Bar :+: CNil) :: HNil = null.asInstanceOf[(A :+: CNil) :: (Bar :+: CNil) :: HNil] // 执行ZipWith操作,完成逐元素转换 val result: (A :+: CNil) :: (Bar :+: CNil) :: HNil = myhlist.zipWith(targetTypePlaceholder)(deembedPoly)
方式2:用柯里化多态函数 + Mapper(无占位实例)
如果不想创建占位实例,可以定义柯里化的多态函数,先绑定目标HList的类型,再对源HList做映射:
import shapeless._ import shapeless.coproduct.Basis import shapeless.ops.hlist.Mapper // 定义柯里化的多态函数,处理单个Coproduct及HList递归转换 object ConvertPoly { // 单个Coproduct的转换逻辑 implicit def single[A <: Coproduct, B <: Coproduct](implicit basis: Basis[A, B]) = at[A](_.deembed[B]) // 递归处理HList的每个元素 implicit def cons[AH <: Coproduct, AT <: HList, BH <: Coproduct, BT <: HList]( implicit headConv: Case.Aux[AH, BH], tailConv: Mapper.Aux[ConvertPoly.type, AT, BT] ) = at[AH :: AT](source => headConv(source.head) :: tailConv(source.tail)) } // 直接通过类型参数约束目标HList类型,执行转换 val result: (A :+: CNil) :: (Bar :+: CNil) :: HNil = myhlist.map(ConvertPoly)
关键注意事项
- 两个HList必须长度相同,且每一对对应位置的
(源Coproduct类型, 目标Coproduct类型)都存在隐式的Basis实例,否则编译会报错。 - 方式2中,Shapeless的
Mapper会自动递归遍历HList的每个元素,为每个元素推导对应的Basis实现转换。
内容的提问来源于stack exchange,提问作者eitaporra
相关产品推荐
相关产品推荐

