Scala collection of classes with Implicit Ordering - scala

I would like to create an Array (or List, ArrayBuffer, etc) which can only contain instance of classes with defined implicit Ordering (e.g. Int, Long, Double).
Something like this:
val ab = new ArrayBuffer[???]()
ab += 7
ab += 8.9
ab += 8L
I don't want to compare these values with each other.

Just use the type class constraint as shown below
def createList[T: Ordering](values: T*) = values.toList
T: Ordering implies only type which has Ordering instance in scope are allowed to be passed as arguments to the function.
scala> def createList[T: Ordering](values: T*) = values.toList
createList: [T](values: T*)(implicit evidence$1: Ordering[T])List[T]
scala> case class Cat()
defined class Cat
scala> createList(1, 2, 3)
res2: List[Int] = List(1, 2, 3)
scala> createList(Cat())
<console>:15: error: No implicit Ordering defined for Cat.
createList(Cat())
^
Integer ordering is available in scope but cat ordering is not available in scope. So you cannot pass Cat values until you provide instance of Ordering[Cat]
Now lets provide some fake ordering and see if compiler accepts Cat as argument
scala> implicit val orderingCat: Ordering[Cat] = (a: Cat, b: Cat) => ???
orderingCat: Ordering[Cat] = $anonfun$1#6be766d1
scala> createList(Cat())
res4: List[Cat] = List(Cat())
It works.

If you really want to have a list of objects of different types and still be able to statically check things about the objects in that list at compile time, you would have to use something like a HList from shapeless. Here is an example of how you could have two heterogeneous lists and still check at compile time that each ith element of both lists can be compared with each other.
import shapeless._
import shapeless.ops.hlist.{LiftAll, Zip, Mapper}
object lt extends Poly1 {
implicit def instance[A] = at[(Ordering[A], A, A)] {
case (ord, a, b) => ord.lt(a, b)
}
}
def areLessThan[L <: HList, O <: HList, OLL <: HList](a: L, b: L)(
implicit
ord: LiftAll.Aux[Ordering, L, O],
zip: Zip.Aux[O :: L :: L :: HNil, OLL],
map: Mapper[lt.type, OLL]
) = zip(ord.instances :: a :: b :: HNil).map(lt)
Using it:
scala> val a = 1 :: "b" :: Option(4L) :: HNil
a: Int :: String :: Option[Long] :: shapeless.HNil = 1 :: b :: Some(4) :: HNil
scala> val b = 2 :: "a" :: Option(7L) :: HNil
b: Int :: String :: Option[Long] :: shapeless.HNil = 2 :: a :: Some(7) :: HNil
scala> areLessThan(a, b)
res10: Boolean :: Boolean :: Boolean :: shapeless.HNil = true :: false :: true :: HNil

Related

Transform a HList of Eithers to an Either of a HList

I want to define a function that accepts a HList whose elements are such that, for each element t, there is a type T such that t: Either[String, T]. The function, which we will call validate, should have the following behaviour:
If all elements of the parameter are Right, return Right of the result of mapping the parameter with right-projection.
Otherwise, return a Left[List[String]], where the list contains the left-projection for each Left in the parameter.
Examples:
validate (Right (42) :: Right (3.14) :: Right (false) :: HNil)
>> Right (42 :: 3.14 :: false :: HNil)
validate (Right (42) :: Left ("qwerty") :: Left ("uiop") :: HNil)
>> Left (List ("qwerty", "uiop"))
An example use case:
case class Result (foo: Foo, bar: Bar, baz: Baz, qux: Qux)
def getFoo: Either[String, Foo] = ???
def getBar: Either[String, Bar] = ???
def getBaz: Either[String, Baz] = ???
def getQux: Either[String, Qux] = ???
def createResult: Either[String, Result] = {
validate (getFoo :: getBar :: getBaz :: getQux :: HNil) match {
case Right (foo :: bar :: baz :: qux :: HNil) => Right (Result (foo, bar, baz, qux))
case Left (errors) => Left ("The following errors occurred:\n" + errors.mkString ("\n"))
}
}
I'll assume we have some test data like this throughout this answer:
scala> import shapeless.{::, HNil}
import shapeless.{$colon$colon, HNil}
scala> type In = Either[String, Int] :: Either[String, String] :: HNil
defined type alias In
scala> val good: In = Right(123) :: Right("abc") :: HNil
good: In = Right(123) :: Right(abc) :: HNil
scala> val bad: In = Left("error 1") :: Left("error 2") :: HNil
bad: In = Left(error 1) :: Left(error 2) :: HNil
Using a custom type class
There are many ways you could do this. I'd probably use a custom type class that highlights the way instances are built up inductively:
import shapeless.HList
trait Sequence[L <: HList] {
type E
type Out <: HList
def apply(l: L): Either[List[E], Out]
}
object Sequence {
type Aux[L <: HList, E0, Out0 <: HList] = Sequence[L] { type E = E0; type Out = Out0 }
implicit def hnilSequence[E0]: Aux[HNil, E0, HNil] = new Sequence[HNil] {
type E = E0
type Out = HNil
def apply(l: HNil): Either[List[E], HNil] = Right(l)
}
implicit def hconsSequence[H, T <: HList, E0](implicit
ts: Sequence[T] { type E = E0 }
): Aux[Either[E0, H] :: T, E0, H :: ts.Out] = new Sequence[Either[E0, H] :: T] {
type E = E0
type Out = H :: ts.Out
def apply(l: Either[E0, H] :: T): Either[List[E0], H :: ts.Out] =
(l.head, ts(l.tail)) match {
case (Right(h), Right(t)) => Right(h :: t)
case (Left(eh), Left(et)) => Left(eh :: et)
case (Left(eh), _) => Left(List(eh))
case (_, Left(et)) => Left(et)
}
}
}
Then you can write validate like this:
def validate[L <: HList](l: L)(implicit s: Sequence[L]): Either[List[s.E], s.Out] = s(l)
And use it like this:
scala> validate(good)
res0: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> validate(bad)
res1: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Left(List(error 1, error 2))
Note that the static types come out right.
Using a right fold
You could also do it a little more concisely by folding with a Poly2.
import shapeless.Poly2
object combine extends Poly2 {
implicit def eitherCase[H, T, E, OutT <: HList]:
Case.Aux[Either[E, H], Either[List[E], OutT], Either[List[E], H :: OutT]] = at {
case (Right(h), Right(t)) => Right(h :: t)
case (Left(eh), Left(et)) => Left(eh :: et)
case (Left(eh), _) => Left(List(eh))
case (_, Left(et)) => Left(et)
}
}
And then:
scala> good.foldRight(Right(HNil): Either[List[String], HNil])(combine)
res2: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> bad.foldRight(Right(HNil): Either[List[String], HNil])(combine)
res3: scala.util.Either[List[String],Int :: String :: shapeless.HNil] = Left(List(error 1, error 2))
I guess this is probably the "right" answer, assuming you want to stick to Shapeless alone. The Poly2 approach just relies on some weird details of implicit resolution (we couldn't define combine as a val, for example) that I personally don't really like.
Using Kittens's sequence
Lastly you could use the Kittens library, which supports sequencing and traversing hlists:
scala> import cats.instances.all._, cats.sequence._
import cats.instances.all._
import cats.sequence._
scala> good.sequence
res4: scala.util.Either[String,Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> bad.sequence
res5: scala.util.Either[String,Int :: String :: shapeless.HNil] = Left(error 1)
Note that this doesn't accumulate errors, though.
If you wanted the most complete possible Typelevel experience I guess you could add a parSequence operation to Kittens that would accumulate errors for an hlist of eithers via the Parallel instance mapping them to Validated (see my blog post here for more detail about how this works). Kittens doesn't currently include this, though.
Update: parallel sequencing
If you want parSequence, it's not actually that much of a nightmare to write it yourself:
import shapeless.HList, shapeless.poly.~>, shapeless.ops.hlist.{Comapped, NatTRel}
import cats.Parallel, cats.instances.all._, cats.sequence.Sequencer
def parSequence[L <: HList, M[_], P[_], PL <: HList, Out](l: L)(implicit
cmp: Comapped[L, M],
par: Parallel.Aux[M, P],
ntr: NatTRel[L, M, PL, P],
seq: Sequencer.Aux[PL, P, Out]
): M[Out] = {
val nt = new (M ~> P) {
def apply[A](a: M[A]): P[A] = par.parallel(a)
}
par.sequential(seq(ntr.map(nt, l)))
}
And then:
scala> parSequence(good)
res0: Either[String,Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> parSequence(bad)
res1: Either[String,Int :: String :: shapeless.HNil] = Left(error 1error 2)
Note that this does accumulate errors, but by concatenating the strings. The Cats-idiomatic way to accumulate errors in a list would look like this:
scala> import cats.syntax.all._
import cats.syntax.all._
scala> val good = 123.rightNel[String] :: "abc".rightNel[String] :: HNil
good: Either[cats.data.NonEmptyList[String],Int] :: Either[cats.data.NonEmptyList[String],String] :: shapeless.HNil = Right(123) :: Right(abc) :: HNil
scala> val bad = "error 1".leftNel[String] :: "error 2".leftNel[Int] :: HNil
bad: Either[cats.data.NonEmptyList[String],String] :: Either[cats.data.NonEmptyList[String],Int] :: shapeless.HNil = Left(NonEmptyList(error 1)) :: Left(NonEmptyList(error 2)) :: HNil
scala> parSequence(good)
res3: Either[cats.data.NonEmptyList[String],Int :: String :: shapeless.HNil] = Right(123 :: abc :: HNil)
scala> parSequence(bad)
res4: Either[cats.data.NonEmptyList[String],String :: Int :: shapeless.HNil] = Left(NonEmptyList(error 1, error 2))
It'd probably be worth opening a PR to add something like this to Kittens.
I managed to arrive at a solution essentially identical to Travis Brown's right-fold solution, with a few additions:
class Validate[E] {
def apply[L <: HList] (hlist: L) (implicit folder: RightFolder[L, Either[List[E], HNil], combine.type]) =
hlist.foldRight (Right (HNil) : Either[List[E], HNil]) (combine)
}
object combine extends Poly2 {
implicit def combine[E, H, T <: HList]
: ProductCase.Aux[Either[E, H] :: Either[List[E], T] :: HNil, Either[List[E], H :: T]] = use {
(elem: Either[E, H], result: Either[List[E], T]) => (elem, result) match {
case (Left (error), Left (errors)) => Left (error :: errors)
case (Left (error), Right (_)) => Left (error :: Nil)
case (Right (_), Left (errors)) => Left (errors)
case (Right (value), Right (values)) => Right (value :: values)
}
}
}
def validate[E] = new Validate[E]
This allows the left type to vary, and allows the syntax:
validate[String] (getFoo :: getBar :: getBaz :: getQux :: HNil) match {
case Right (foo :: bar :: baz :: qux :: HNil) => ???
case Left (errors) => ???
}
Admittedly this is the first time that I have used Poly. It blew my mind to see that this actually worked. Infuriatingly, the static analysis provided by my IDE (IntelliJ) is not clever enough to infer the types of the terms in the match cases.

How can I apply an HList of arbitrary functions to an arbitrary value?

I'd like to be able to apply an arbitrary list of Function1[I, ?]s to an arbitrary input I. This is what I have so far:
type StringInputFunction[T] = Function[String, T]
val strLen: String => Int = _.length
val strRev: String => String = _.reverse
val functions = strLen :: strRev :: HNil
val expected = 4 :: "evif" :: HNil
object applyTo5 extends (StringInputFunction ~> Id) {
override def apply[T](f: StringInputFunction[T]): Id[T] = f("five")
}
def applyFunctionsTo5[FH <: HList, OH <: HList](fs: FH)
(implicit constrain: UnaryTCConstraint[FH, StringInputFunction],
mapper: Mapper.Aux[applyTo5.type, FH, OH]): mapper.Out = {
fs.map(applyTo5)
}
applyFunctionsTo5(functions) shouldBe expected
class ApplyTo(string: String) extends (StringInputFunction ~> Id) {
override def apply[T](f: StringInputFunction[T]): Id[T] = f(string)
}
def applyFunctionsTo[FH <: HList, OH <: HList]
(fs: FH, input: String)
(implicit constrain: UnaryTCConstraint[FH, StringInputFunction],
mapper: Mapper.Aux[ApplyTo, FH, OH]): mapper.Out = {
val applyTo = new ApplyTo(input)
fs.map(applyTo)
}
applyFunctionsTo(functions, "five") shouldBe expected
This results in the compile error:
ShapelessSpec.scala:81: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[applyTo.type,FH]
fs.map(applyTo)
ShapelessSpec.scala:83: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper.Aux[ApplyTo,shapeless.::[String => Int,shapeless.::[String => String,shapeless.HNil]],OH]
applyFunctionsTo(functions, "five") shouldBe expected
How can I fix this to work with any String input?
Can I change genericise this even further to work with any input type T?
I was thinking I'd done this exact operation before, and was able to find this gist from a few years ago. To summarize my example there, you can do this pretty nicely using only operations that are already provided by Shapeless, in a manner that looks a lot like how you'd do something like this with ordinary lists at the value level. Suppose you have the following setup:
import shapeless.{ ::, HNil }
val strLen: String => Int = _.length
val strRev: String => String = _.reverse
val functions = strLen :: strRev :: HNil
Then you can write this:
scala> functions.zipApply(functions.mapConst("five"))
res0: Int :: String :: shapeless.HNil = 4 :: evif :: HNil
Or this:
scala> def foo(in: String) = functions.zipApply(functions.mapConst(in))
foo: (in: String)Int :: String :: shapeless.HNil
scala> foo("six")
res1: Int :: String :: shapeless.HNil = 3 :: xis :: HNil
This will work with any hlists of functions from a particular type applied to that particular type.
The gist gives a few alternative approaches, but zipApply plus mapConst feels by far the best to me.

Taking HList of Seq[_] and generating Seq[HList] with cartesian product of values

I'm new to Shapless.
I'm trying to write a function that would take an HList of sequences of different types, convert it to a Seq[HList] containing the cartesian product of the original HList's elements, and iterate over the resulting Sequence
For example:
val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil
cartesianProduct: Seq[Boolean :: Int :: String :: HNil] = Seq(
true :: 1 :: foo :: HNil,
true :: 1 :: bar :: HNil,
true :: 2 :: foo :: HNil,
true :: 2 :: bar :: HNil,
true :: 3 :: foo :: HNil,
true :: 3 :: bar :: HNil,
false :: 1 :: foo :: HNil,
false :: 1 :: bar :: HNil,
false :: 2 :: foo :: HNil,
false :: 2 :: bar :: HNil,
false :: 3 :: foo :: HNil,
false :: 3 :: bar :: HNil)
I was able to achieve this in Intellij Scala Worksheet using the following code:
import shapeless._
import shapeless.ops.hlist.LeftFolder
object combine extends Poly {
implicit def `case`[T <: HList, S] = use((acc : Seq[T], curr : Seq[S]) => {
for {
el <- curr
v <- acc
} yield el :: v
})
}
val input = Seq(true, false) :: Seq(1,2,3)::Seq("foo", "bar") :: HNil
val combinations = input.foldLeft(Seq[HNil](HNil))(combine)
combinations.foreach(println)
Here everything works, I assume, because the full type of input is known to the compiler.
However, when I try to wrap the whole operation in a function, the full type of input gets lost, and I can't call foreach on the result of foldLeft:
def cartesian[T <: HList](input: T)
(implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
input.foldLeft(Seq[HNil](HNil))(combine)
.foreach(println)
}
The compiler complains:
value foreach is not a member of folder.Out
input.foldLeft(Seq[HNil](HNil))(combine).foreach(println)
^
I imagine there is some implicit evidence I can request to assert the correct shape of input (HList of Seq[_]) and thus get the compiler to figure out the resulting type of the foldLeft, but I can't figure out what it could be...
Hope someone can help me figure this out.
Thanks.
Update:
My eventual goal with this question was, given an HList of Seq[_] to derive a function (perhaps on a case class) that will accept a function with the same arg arity as the input HList and the argument types matching the 'Seq' element types in the same order. For example for the input above the function would be f: (Boolean, Int, String) => R
So in affect I would be able to iterate over the cartesian product of the input using f.
The final code looks like this:
import shapeless._
import shapeless.ops.function.FnToProduct
import shapeless.ops.hlist.LeftFolder
import shapeless.syntax.std.function.fnHListOps
object combine extends Poly {
implicit def `case`[EL <: HList, S] = use((acc : Seq[EL], curr : Seq[S]) => {
for {
el <- curr
v <- acc
} yield el :: v
})
}
case class Cartesian[R <: HList, F, FR](combinations: Seq[R])
(implicit ftp: FnToProduct.Aux[F, R => Unit]) {
def foreach(f: F) = combinations.foreach(f.toProduct)
}
def cartesian[T <: HList, R <: HList, F, FR](variants: T)(implicit
folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[R]],
fnToProd: FnToProduct.Aux[F, R => Unit]
) = {
val combinations: Seq[R] = variants.foldLeft(Seq[HNil](HNil))(combine)
Cartesian(combinations)
}
val variants = Seq(true, false) :: Seq("foo", "bar") :: Seq(1, 2, 3) :: HNil
cartesian(variants).foreach((a, b, c) => println(s"$a, $b, $c"))
Note that the types for the function arguments a, b, c are correctly inferred and are Boolean, String, and Int.
Currently the result type of the function passed into foreach has to be fixed (in the code above it is Unit). It can't be inferred from the function passed in.
The problem is not that compiler doesn't know anything about input, but rather that it does not know anything about output.
Inside def cartesian all that is known to compiler is that after foldLeft you get some type folder.Out (which depends on an instance that shapeless would figure for you).
For ensuring result types, you can use LeftFolder.Aux with one extra type parameter, e.g.
def cartesian[T <: HList](input: T)
(implicit folder: LeftFolder.Aux[T, Seq[HNil], combine.type, _ <: Seq[Any]]) = {
input.foldLeft(Seq[HNil](HNil))(combine)
.foreach(println)
}
Now the compiler will know that result is some subtype of Seq[Any], so it is possible to call foreach on it.
Of course, this is only the problem inside the def. At call sites output types will be resolved based on input, so you would be able to do this without Aux:
def cartesian2[T <: HList](input: T)
(implicit folder: LeftFolder[T, Seq[HNil], combine.type]) = {
input.foldLeft(Seq[HNil](HNil))(combine)
}
cartesian2(input).foreach(println)
Runnable code: https://scalafiddle.io/sf/n409yNW/2

Convert Sized[Something[String], _] collection to HList

I'm new to shapeless and trying to learning it by doing. I want to make a very small library which can convert a collection (in first step a Sized collection) of String, to HList of different types.
basically what I want to achieve:
import shapeless._
import nat._
import BigQueryParser._
val s: Sized[IndexedSeq[String], nat._3] = Sized("Testing", "2.0", "1")
BigQueryParser[Sized[IndexedSeq[String], nat._3], String :: BigDecimal :: BigInt :: HNil].parse(s)
My non-working implementation is here https://gist.github.com/taojang/f6a9352dbc618039e3a3
I implemented it after https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/csv.scala
My code does not compile, compiler complains about following errors:
[error] /somepath/some-file.scala: could not find implicit value for parameter st: exmaple.BigQueryParser[shapeless.Sized[IndexedSeq[String],shapeless.nat._3],shapeless.::[String,shapeless.::[BigDecimal,shapeless.::[BigInt,shapeless.HNil]]]]
[error] BigQueryParser[Sized[IndexedSeq[String], nat._3], String :: BigDecimal :: BigInt :: HNil].parse(s)
I find that the best way to debug these kinds of issues is to try to build your instances by hand. For example, this is fine:
scala> BigQueryParser[Sized[IndexedSeq[String], _0], HNil]
res0: BigQueryParser[shapeless.Sized[IndexedSeq[String],shapeless.nat._0],shapeless.HNil] = BigQueryParser$$anon$6#2f4cd46d
But this breaks:
scala> deriveHCons[IndexedSeq[String], _0, String, HNil]
<console>:31: error: could not find implicit value for parameter conv: BigQueryParser[scala.collection.generic.IsTraversableLike[IndexedSeq[String]]#A,String]
deriveHCons[IndexedSeq[String], _0, String, HNil]
^
Which suggests that something is going wrong with the IsTraversableLike[Repr]#A type projection. The first thing I'd do in a situation like that is make it a type parameter (ReprA in this case) and then constrain the IsTraversableLike[Repr] instance with a refinement type:
implicit def deriveHCons[Repr, ReprA, L <: Nat, V, T <: HList](implicit
itl: IsTraversableLike[Repr] { type A = ReprA },
ev: AdditiveCollection[Repr],
ts: BigQueryParser[Sized[Repr, L], T],
conv: BigQueryParser[ReprA, V]
): BigQueryParser[Sized[Repr, Succ[L]], V :: T] =
new BigQueryParser[Sized[Repr, Succ[L]], V :: T] {
def parse(s: Sized[Repr, Succ[L]]): Try[V :: T] =
for {
h <- conv.parse(s.head)
t <- ts.parse(s.tail)
} yield h :: t
}
And that works just fine:
scala> println(
| BigQueryParser[
| Sized[IndexedSeq[String], nat._3],
| String :: BigDecimal :: BigInt :: HNil
| ].parse(s)
| )
Success(Testing :: 2.0 :: 1 :: HNil)
There may be other simplifications you could make (e.g. using _0 directly instead of a L <: _0 type parameter), but this fix should at least get things rolling.

Inferred type of function that zips HLists

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