【问题标题】:How to define an HList type, but based on another HList type如何定义一个 HList 类型,但基于另一个 HList 类型
【发布时间】:2020-12-13 20:04:51
【问题描述】:

假设我有一个案例类:

case class Foo(num: Int, str: String, bool: Boolean)

现在我还有一个简单的包装器:

sealed trait Wrapper[T]
case class Wrapped[T](value: T) extends Wrapper[T]

(以及其他一些在这里不重要的 Wrapper 实现)

我可以使用 Generic[Foo] 来获得一个代表这个案例类的 Aux: val genFoo = Generic[Foo] (在我的真实代码中,我使用 LabelledGeneric 以免丢失字段名称)

这为我提供了一个表示 HList 的定义的类型:

Generic.Aux[Foo, Int :: String :: Boolean :: HNil]

(与 LabelledGeneric 一起使用时,定义要复杂得多,但本质相同)

现在我想为 HList 创建一个类型定义,而不是原始类型,而是包含包装类型。示例:

type WrappedHlist = Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil

然后我可以使用这个类型定义来生成一个 Circe 编码器/解码器(我已经为 Wrapper 类型提供了必要的编码器/解码器)。

所有必要的信息都存在于编译时,因为第二个 HList 的定义很容易从第一个中确定。现在,我可以通过手动写出定义来实现这一点,或者通过使用冗余版本重复案例类 Foo 的定义,其中所有内容都定义为 Wrappers。如何创建一个不需要我重复所有内容的定义?

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    嗯,这可以通过一些类型类、路径相关类型和 Aux 模式来实现:

    trait WrapperHelper[In] {
      type Out
      def wrap(i: In): Out
    }
    object WrapperHelper {
      type Aux[I, O] = WrapperHelper[I] { type Out = O }
    
      implicit val nilWH: WrapperHelper.Aux[HNil, HNil] = new WrapperHelper[HNil] {
        type Out = HNil
        def wrap(i: HNil): HNil = i
      }
    
      implicit def hconsWH[H, TI <: HList, TO <: HList](
        implicit
        tailWH: WrapperHelper.Aux[TI, TO]
      ): WrapperHelper.Aux[H :: TI, Wrapper[H] :: TO] = new WrapperHelper[H :: TI] {
        type Out = Wrapper[H] :: TO
        def wrap(i: H :: TI): Wrapper[H] :: TO = i match {
          case head :: tail => Wrapped(head) :: tailWH.wrap(tail)
        }
      }
    }
    
    def wrap[I](i: I)(implicit wh: WrapperHelper[I]): wh.Out = wh.wrap(i)
    

    HNIl 被视为 In = Out 的特殊情况。对于其他一切......您仍然必须递归地映射类型。虽然不漂亮,但这应该可以满足您的要求:

    @ wrap(Generic[Foo].to(Foo(1, "", true)))
    res7: Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil = Wrapped(1) :: Wrapped("") :: Wrapped(true) :: HNil
    

    假设你想提供一些自定义的编码/解码逻辑,你应该修改签名

    implicit def hconsWH[H, TI <: HList, TO <: HList](
      implicit
      tailWH: WrapperHelper.Aux[TI, TO]
    ): WrapperHelper.Aux[H :: TI, Wrapper[H] :: TO]
    

    类似

    implicit def hconsWH[H, TI <: HList, TO <: HList](
      implicit
      thatThingINeedToLiftAToWrapperA: Encoder[A], // whatever is needed to lift A => Wrapper[A]
      tailWH: WrapperHelper.Aux[TI, TO]
    ): WrapperHelper.Aux[H :: TI, Wrapper[H] :: TO]
    

    即使您不需要实现,在这里定义类型类仍然很有用,只是为了提供一些可以为您解析类型的隐含证据。

    【讨论】:

      【解决方案2】:

      HList Int :: String :: Boolean :: HNil 可以使用标准类型类 shapeless.ops.hlist.Mapped 轻松转换为 HList Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil

      implicitly[Mapped.Aux[Int :: String :: Boolean :: HNil, 
        Wrapper, 
        Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil]] // compiles
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-18
        • 1970-01-01
        • 2014-06-03
        • 1970-01-01
        • 2017-02-10
        相关资源
        最近更新 更多