简体   繁体   中英

Transform an HList into an HList of HLists

How do I transform an HList into an HList of HLists as in the following snippet.

import shapeless._
import Nat._

case class A(i: Int)
case class B(str: String)
case class C(i: Int, str: String)

type Input = A :: B :: C :: HNil
val in: Input = A(1) :: B("b") :: C(2, "c") :: HNil

type X = A :: HNil
val x: X = A(1) :: HNil

type Y = A :: B :: HNil // could also be B :: HNil
val y: Y = A(1) :: B("b") :: HNil

type Z = A :: C :: HNil // could also be B :: C :: HNil
val z: Z = A(1) :: C(2, "c") :: HNil

type Output = X :: Y :: Z :: HNil
val out: Output = x :: y :: z :: HNil

// Illustrates what I want to accomplish.
def build(in: Input) : Output = {
  val x: X = in(_0) :: HNil
  val y: Y = in(_0) :: in(_1) :: HNil
  val z: Z = in(_0) :: in(_2) :: HNil
  x :: y :: z :: HNil
}

println(build(in) == out) // true


def magic[In <: HList, Out <: HList](in: In) : Out = ???
println(magic[Input, Output](in) == out)

I want to build Output given Input by way of the magic method that somehow maps over the input and ends up with what build outputs.

This isn't too bad with a custom type class. Note that in a sense we need two "base cases"—one to kick off the top-level HList , and one to start each individual inner HList . The inductive step then shows how to add a new item (that we know how to extract from the input) to the last HList we've added.

import shapeless._, ops.hlist.Selector

trait Picker[I <: HList, O <: HList] {
  def apply(i: I): O
}

object Picker {
  implicit def hnilPicker[I <: HList]: Picker[I, HNil] = new Picker[I, HNil] {
    def apply(i: I) = HNil
  }

  implicit def hnilHlistPicker[I <: HList, OT <: HList](implicit
    picker: Picker[I, OT]
  ): Picker[I, HNil :: OT] = new Picker[I, HNil :: OT] {
    def apply(i: I) = HNil :: picker(i)
  }

  implicit def hlistPicker[I <: HList, OHH, OHT <: HList, OT <: HList](implicit
    sel: Selector[I, OHH],
    picker: Picker[I, OHT :: OT]
  ): Picker[I, (OHH :: OHT) :: OT] = new Picker[I, (OHH :: OHT) :: OT] {
    def apply(i: I) = picker(i) match {
      case h :: t => (sel(i) :: h) :: t
    }
  }
}

And then:

def magic[In <: HList, Out <: HList](in: In)(implicit
  picker: Picker[In, Out]
): Out = picker(in)

And finally:

scala> println(magic[Input, Output](in))
A(1) :: HNil :: A(1) :: B(b) :: HNil :: A(1) :: C(2,c) :: HNil :: HNil

scala> println(magic[Input, Output](in) == out)
true

It'd be nice to be able to specify only the output type and have the input type inferred, but there's unfortunately no convenient way to implement that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM