Thanks to https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0 I understand how to zip shapeless HLists:
Import some stuff from Shapeless 2.0.0-M1:
import shapeless._
import shapeless.ops.hlist._
import syntax.std.tuple._
import Zipper._
Create two HLists:
scala> val h1 = 5 :: "a" :: HNil
h1: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 5 :: a :: HNil
scala> val h2 = 6 :: "b" :: HNil
h2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 6 :: b :: HNil
Zip them:
scala> (h1, h2).zip
res52: ((Int, Int), (String, String)) = ((5,6),(a,b))
Now try to define a function that does the same thing:
scala> def f[HL <: HList](h1: HL, h2: HL) = (h1, h2).zip
f: [HL <: shapeless.HList](h1: HL, h2: HL)Unit
The inferred return type is Unit, and indeed applying f to h1 and h2 does just that:
scala> f(h1, h2)
scala>
Is there a way to define f such that I get ((5,6),(a,b)) back in this case?
Ultimately what I'm trying to do is define a function that zips the two HLists and then maps over them, choosing either _1 or _2 based a coin toss, which would yield another HL.
object mix extends Poly1 {
implicit def caseTuple[T] = at[(T, T)](t =>
if (util.Random.nextBoolean) t._2 else t._1)
}
Which works fine in the REPL:
scala> (h1, h2).zip.map(mix)
res2: (Int, String) = (5,b)
But I'm getting tripped up on the above issue when trying to pull this into a function.
Thanks!
You can wrap everything up in one method using the Zip
(or in this case Zip.Aux
) type class:
import shapeless._, shapeless.ops.hlist._
object mix extends Poly1 {
implicit def caseTuple[T] = at[(T, T)](t =>
if (util.Random.nextBoolean) t._2 else t._1)
}
def zipAndMix[L <: HList, Z <: HList](h1: L, h2: L)(implicit
zipper: Zip.Aux[L :: L :: HNil, Z],
mapper: Mapper[mix.type, Z]
) = (h1 zip h2) map mix
Now assuming you have h1
and h2
defined as in the question, you can write this:
scala> zipAndMix(h1, h2)
res0: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 5 :: b :: HNil
scala> zipAndMix(h1, h2)
res1: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 6 :: a :: HNil
scala> zipAndMix(h1, h2)
res2: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 5 :: a :: HNil
And so on. This will work in either 2.0.0-M1 or the latest snapshot, although (as I've noted in a comment above) you may run into confusing issues on the way before this bug was fixed.
Given the compiler error and some perusing the tests in hlist.scala, zip is defined this way:
def f[L <: HList, OutT <: HList](l : L)(
implicit transposer : Transposer.Aux[L, OutT],
mapper : Mapper[tupled.type, OutT]) = l.transpose.map(tupled)
And the application of my mix
can be defined this way:
def g[L <: HList](l : L)(
implicit mapper: Mapper[mix.type,L]) = l.map(mix)
The composition does what I was looking for:
scala> g(f(h1 :: h2 :: HNil))
res12: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 5 :: b :: HNil
scala> g(f(h1 :: h2 :: HNil))
res13: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 6 :: a :: HNil
scala> g(f(h1 :: h2 :: HNil))
res14: shapeless.::[Int,shapeless.::[String,shapeless.HNil]] = 6 :: b :: HNil