You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 21:20:59