如何使用foldRight/foldLeft将HList转换为另一个HList

6
这个问题源于我的之前的提问:HList#foldLeft()返回什么? 我有一个这样的场景:
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
}

这段代码试图读取多个列的值。

如果我调用readColumns(cursor, new Column[String] :: new Column[Int] :: HNil),我期望得到String :: Int :: HNil

readColumns()方法编译没有问题,但编译器在具体的调用中抱怨隐式参数。

正确的工作方式是什么?

更新1:

以下是我使用2列时收到的确切错误消息:

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)]

不知道如何帮助编译器。:(

更新2:

问题:为什么在readColumns()的隐式参数中指定HNil.typeRightFolder.Aux[A, (HNil.type, Cursor, Int), readColumn.type, (B, Cursor, Int)]

1个回答

6

更新以解决编辑问题

以下是一个完整的工作示例:

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
}

然后:

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)

这段代码可以在2.0.0和2.1.0-RC1上都编译通过,并且表现符合预期。

我应该在我的原始回答中提到,像那样使用HNil.type并不理想——它可以正常工作,但是在foldRight的参数中显式地将HNil设置为HNil是更好的解决方案。请注意,你必须选择其中一种,因为HNil的静态类型是HNil.type,而RightFolder在第二个参数上不是协变的。

原始答案

你的readColumn定义中有一个非常小的错误——你返回了一个Tuple4,但是你需要返回一个Tuple3。以下代码应该可以正常工作:

    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)
       }
    }

通常为了与隐式解析相关的原因,对于任何隐式方法都提供显式返回类型是一个好主意,但在这种情况下,关于返回类型明确的表述也很快揭示了错误。

谢谢,现在好多了。为什么我必须为readColumns()的隐式参数指定HNil.type?对于具体的调用仍然无法正常工作。 :-( - david.perez
1
嗯,当我设置一个简单的示例时,调用readColumns对我起作用了。我会看一下,但可能要等到今天下午以后才有机会。 - Travis Brown
我已经更新了问题,并提供了我收到的精确错误信息,以便更好地进行诊断。 - david.perez
请注意,在表达式 HNil: HNil 中,第一个术语指的是 对象 HNil,而第二个术语指的是 特质 HNil - david.perez
你的样例在Scala REPL中可以正常工作,但在常规源文件中却不行。我一直遇到编译器无法生成readColumns()隐式参数的问题。Shapeless很强大,但也很棘手。 :-( - david.perez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接