Reputation: 9072
How to zip an HList with, let's say, a Scala Stream to produce an HList of pairs? Specifically:
import shapeless._
val a = 1.3 :: true :: "z" :: HNil
val b = Stream.from(1)
val ab: (Double, Int) :: (Boolean, Int) :: (String, Int) :: HNil = ???
Upvotes: 0
Views: 250
Reputation: 51271
It's possible, but there are restrictions. Mostly you have to specify the desired length of the Scala collection to be zipped. The result will, of course, be the length of the shorter of the two HLists.
import shapeless._
import syntax.std.traversable._ // toHList
val a = 1.3 :: true :: "z" :: HNil // 3 elements
val short_b = Stream.from(1).take(2).toHList[Int::Int::HNil]
val long_b = Stream.from(7).take(5).toHList[Int::Int::Int::Int::Int::HNil]
toHList
returns an Option[HList]
so we'll map
over the results to extract the HList
to be zipped.
short_b.map(a zip _) // 2 element result
//res0: Option[(Double, Int) :: (Boolean, Int) :: HNil] =
// Some((1.3,1) :: (true,2) :: HNil)
long_b.map(a zip _) // 3 element result
//res1: Option[(Double, Int) :: (Boolean, Int) :: (String, Int) :: HNil] =
// Some((1.3,7) :: (true,8) :: (z,9) :: HNil)
Upvotes: 1
Reputation: 18424
trait Zipper[A <: HList, B] {
type Out <: HList
def zip(a: A, bs: Stream[B]): Out
}
implicit def hnilZipper[B] = new Zipper[HNil, B] {
type Out = HNil
def zip(a: HNil, bs: Stream[B]): HNil = HNil
}
implicit def consZipper[Head, Tail <: HList, B](implicit z: Zipper[Tail, B]) = new Zipper[Head :: Tail, B] {
type Out = (Head, B) :: z.Out
def zip(a: Head :: Tail, bs: Stream[B]): Out = {
(a.head, bs.head) :: z.zip(a.tail, bs.tail)
}
}
def zip[A <: HList, B](a: A, b: Stream[B])(implicit z: Zipper[A, B]): z.Out = z.zip(a, b)
As Marth notes, there are safety issues and this will fail if the Stream is shorter than the HList. But you could pretty easily modify this to return an Option[(A1, B) :: (A2, B) :: ...]
or a (A1, Option[B]) :: (A2, Option[B]) :: ...
if that's a concern.
Upvotes: 1