如何将嵌套的HList of HLists结构扁平化为类型安全的单层HList
嵌套HList扁平化实现方案
可以基于Shapeless自定义递归类型类实现该需求,同时满足运行时结构转换、静态类型自动正确推导两个要求。
场景示例
运行时获取的嵌套HList实例示例:
(2 :: HNil) :: (1001 :: HNil) :: (1001 :: HNil) :: HNil
对应静态类型:
(Int :: HNil) :: (Long :: HNil) :: (Long :: HNil) :: HNil
这类嵌套HList的结构、元素类型在运行时不固定,也可能为如下形式:
(Int :: HNil) :: (String :: HNil) :: HNil
需要将其扁平化为单层HList,以上述第一个示例为例,目标输出值为:
2 :: 1001 :: 1001 :: HNil
对应目标静态类型为:
Int :: Long :: Long :: HNil
具体实现
核心思路是通过类型类递归拆解HList结构,区分头元素是否为HList做对应处理,借助Shapeless自带的Prepend完成HList拼接,自动推导最终类型:
import shapeless._ // 定义扁平化类型类,输入为嵌套HList类型IN,输出为扁平化后HList类型OUT trait Flatten[IN <: HList, OUT <: HList] { def apply(input: IN): OUT } // 基础规则:空HList扁平化后仍为空HList implicit val hnilFlatten: Flatten[HNil, HNil] = (input: HNil) => HNil // 递归规则1:当前头元素本身是HList,先打平头元素、再打平尾部,最后拼接两个打平结果 implicit def hlistHeadFlatten[H <: HList, HOut <: HList, T <: HList, TOut <: HList]( implicit flattenHead: Flatten[H, HOut], flattenTail: Flatten[T, TOut], prepend: Prepend[HOut, TOut] ): Flatten[H :: T, prepend.Out] = (input: H :: T) => { val head :: tail = input prepend(flattenHead(head), flattenTail(tail)) } // 递归规则2:当前头元素是普通非HList值,将其放入结果头部,拼接打平后的尾部 implicit def plainHeadFlatten[H, T <: HList, TOut <: HList]( implicit flattenTail: Flatten[T, TOut], prepend: Prepend[H :: HNil, TOut] ): Flatten[H :: T, prepend.Out] = (input: H :: T) => { val head :: tail = input prepend(head :: HNil, flattenTail(tail)) } // 对外调用封装 def flatten[IN <: HList, OUT <: HList](input: IN)(implicit f: Flatten[IN, OUT]): OUT = f(input)
使用效果
直接调用flatten方法即可,编译器会自动推导扁平化后的正确静态类型:
// 测试两层嵌套示例 val nested1 = (2 :: HNil) :: (1001L :: HNil) :: (1001L :: HNil) :: HNil val flat1 = flatten(nested1) // flat1 静态类型为 Int :: Long :: Long :: HNil,运行时值为 2 :: 1001 :: 1001 :: HNil // 测试不同元素类型的嵌套示例 val nested2 = (2 :: HNil) :: ("demo" :: HNil) :: HNil val flat2 = flatten(nested2) // flat2 静态类型为 Int :: String :: HNil,运行时值为 2 :: "demo" :: HNil
该实现不限制嵌套层级,三层及以上的嵌套HList也可以正确打平,类型推导全程由编译器完成,无运行时类型安全问题。
内容的提问来源于stack exchange,提问作者Shivam
相关产品推荐
相关产品推荐

