Question

This is essentially what I want:

case class Foo[T](x: T)

object combine extends Poly {
  implicit def caseFoo[A, B] = use((f1: Foo[A], f2: Foo[B]) => Foo((f1.x, f2.x)))
}

def combineHLatest[L <: HList](l: L) = l.reduceLeft(combine)

So combineHLatest(Foo(1) :: Foo("hello") :: HNil) should yield Foo( (1, "hello") )

The above doesn't compile as it cannot find an implicit LeftReducer but I'm at a loss as to how to implement one.

Was it helpful?

Solution

It was pretty late last night when I answered this, and while the information in the original answer below is correct, it's not necessarily the most helpful presentation.

There's a good reason you should be at a loss as to how to implement the LeftReducer, since that's not your job. The compiler will create any valid instance of the type class you need—you just have to make sure it has all the information it needs.

For example, the following works just fine with your implementation:

scala> (Foo(1) :: Foo("hello") :: HNil).reduceLeft(combine)
res0: Foo[(Int, String)] = Foo((1,hello))

Here the compiler can see the type of the HList you want to reduce, and can create the appropriate LeftReducer instance.

When you wrap the call to leftReduce up in a method, on the other hand, the compiler doesn't know anything about the list you're calling it on except what you explicitly tell it. In your implementation of combineHLatest, the compiler knows that L is an HList, but that's it—it doesn't have any evidence that it can perform the reduction. Fortunately it's pretty easy to give it this evidence via an implicit parameter (see the original answer below).


I'd originally posted a kind of clunky solution to the flattened-tuple problem here, but the clunkiness was only because of a small typo in my original attempt. It's actually possible to write a fairly elegant implementation:

def combineHLatest[L <: HList, R <: HList](l: L)(implicit
  r: RightFolder.Aux[L, Foo[HNil], combine.type, Foo[R]],
  t: Tupler[R]
) = Foo(l.foldRight(Foo(HNil: HNil))(combine).x.tupled)

(My mistake was writing R instead of Foo[R] as the last type parameter on the Aux.)


Original answer

This will work as expected if you make sure your method has evidence that it can perform the reduction on the input:

import shapeless._, ops.hlist.LeftReducer

def combineHLatest[L <: HList](l: L)(implicit r: LeftReducer[L, combine.type]) =
  l.reduceLeft(combine)

Note, though, that this approach will just build up a nested tuple if you have more than two arguments, so you may want something more like this:

object combine extends Poly {
  implicit def caseFoo[A, B <: HList] = use(
    (f1: Foo[A], f2: Foo[B]) => Foo(f1.x :: f2.x)
  )
}

def combineHLatest[L <: HList](l: L)(implicit
  r: RightFolder[L, Foo[HNil], combine.type]
) = l.foldRight(Foo(HNil: HNil))(combine)

And then for example:

scala> println(combineHLatest(Foo(1) :: Foo("hello") :: Foo('a) :: HNil))
Foo(1 :: hello :: 'a :: HNil)

If you wanted a (flattened) tuple instead, that'd also be pretty straightforward.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top