Inferred type of function that zips HLists
Asked Answered
B

2

4

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!

Baritone answered 7/12, 2013 at 22:50 Comment(3)
I'm not around a computer but the Unit issue is a bug that's been fixed since the first milestone. Now those examples won't compile, and the error message should tell you the Zip instance you need to add to your method definition.Pegeen
Thanks, Travis. I am seeing an error now when using 2.0.0-SNAPSHOT. I may still need a hint or two to accomplish my goal, but this new error is helpful.Baritone
Specifically, that error after defining f as shown above is: <console>:24: error: could not find implicit value for parameter transpose: shapeless.ops.tuple.Transposer[(HL, HL)] def f[HL <: HList](h1: HL, h2: HL) = (h1, h2).zipBaritone
P
7

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.

Pegeen answered 8/12, 2013 at 10:7 Comment(0)
B
2

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
Baritone answered 8/12, 2013 at 9:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.