leftReduce Shapeless HList of generic types

前端 未结 1 1644
粉色の甜心
粉色の甜心 2021-01-05 11:03

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         


        
相关标签:
1条回答
  • 2021-01-05 11:27

    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.

    0 讨论(0)
提交回复
热议问题