Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to transform an HList to another HList with foldRight/foldLeft

This question is derived from my previous question: What does HList#foldLeft() return?

I have this scenario:

class Cursor {
}

trait Column[T] {
   def read(c: Cursor, index: Int): T
}

object Columns {
    object readColumn extends Poly2 {
        implicit def a[A, B <: HList] = at[Column[A], (B, Cursor, Int)] { case (col, (values, cursor, index)) ⇒
            (col.read(cursor, index) :: values, cursor, index+1)
        }
    }

    def readColumns[A <: HList, B <: HList](c: Cursor, columns: A)(implicit l: RightFolder.Aux[A, (HNil.type, Cursor, Int), readColumn.type, (B, Cursor, Int)]): B =
        columnas.foldRight((HNil, c, 0))(readColumn)._1
}

This code, tries to read the values of several columns.

If I call readColumns(cursor, new Column[String] :: new Column[Int] :: HNil), I expect to get String :: Int :: HNil.

The readColumns() method compiles ok, but the compiler complains about implicits in concrete invocations.

What is the right way of working?.

UPDATE 1:

Here is the exact error message I'm receiving when invoking with 2 columns:

could not find implicit value for parameter l: 
shapeless.ops.hlist.RightFolder.Aux[shapeless.::[Column[String],shapeless.::
[Column[String],shapeless.HNil]],(shapeless.HNil.type, android.database.Cursor, Int),readColumn.type,(B, android.database.Cursor, Int)]

Don't know how to help the compiler. :-(

UPDATE 2:

Question: why specify HNil.type in the implicit parameter of readColumns(): RightFolder.Aux[A, (HNil.type, Cursor, Int), readColumn.type, (B, Cursor, Int)] ?

like image 587
david.perez Avatar asked Oct 29 '14 13:10

david.perez


1 Answers

Update to address edits

Here's a complete working example:

class Cursor {}

trait Column[T] {
   def read(c: Cursor, index: Int): T
}

import shapeless._, ops.hlist.RightFolder

object Columns {
  object readColumn extends Poly2 {
    implicit def a[A, B <: HList]: Case.Aux[
      Column[A],
      (B, Cursor, Int),
      (A :: B, Cursor, Int)
    ] = at[Column[A], (B, Cursor, Int)] {
      case (col, (values, cursor, index)) =>
        (col.read(cursor, index) :: values, cursor, index + 1)
    }
  }

  def readColumns[A <: HList, B <: HList](c: Cursor, columns: A)(implicit
    l: RightFolder.Aux[
      A,
      (HNil, Cursor, Int),
      readColumn.type,
      (B, Cursor, Int)
    ]
  ): B = columns.foldRight((HNil: HNil, c, 0))(readColumn)._1
}

And then:

val stringColumn = new Column[String] {
  def read(c: Cursor, index: Int) = "foo"
}

val intColumn = new Column[Int] {
  def read(c: Cursor, index: Int) = 10
}

Columns.readColumns(new Cursor, stringColumn :: intColumn :: HNil)

This compiles just fine and does what I'd expect on both 2.0.0 and 2.1.0-RC1.

And I should have mentioned in my original answer that using HNil.type like that isn't ideal—it works just fine, but explicitly typing the HNil in the argument to foldRight as HNil is a better solution. Note that you have to do one or the other, since the static type of HNil is HNil.type, and RightFolder isn't covariant in its second argument.

Original answer

There's a very minor error in your readColumn definition—you're returning a Tuple4, but you want to return a Tuple3. The following should work:

    object readColumn extends Poly2 {
       implicit def a[A, B <: HList]: Case.Aux[
         Column[A],
         (B, Cursor, Int),
         (A :: B, Cursor, Int)
       ] = at[Column[A], (B, Cursor, Int)] {
          case (col, (values, cursor, index)) =>
           (col.read(cursor, index) :: values, cursor, index+1)
       }
    }

It's usually a good idea to provide an explicit return type for any implicit method for unrelated reasons having to do with implicit resolution, but in this case being explicit about the return type also turns up the error pretty quickly.

like image 190
Travis Brown Avatar answered Sep 24 '22 20:09

Travis Brown