【问题标题】:Cannot convert HList to tuple after Shapeless FoldRightShapeless FoldRight 后无法将 HList 转换为元组
【发布时间】:2019-12-06 16:31:52
【问题描述】:

我正在尝试使用 Scala 将 CSV 文件解析器创建到案例类中,并且我正在尝试使用 Shapeless 使其具有通用性。

我希望我的解析器允许用户指定提取函数extract: CsvRow => String,而不是为特定字段类型提供一对一的对应和类型类,因为我正在解析的文件具有不同的格式和解析操作适用于一个文件的文件不适用于另一个文件。 我想我是否可以通过首先进行“愚蠢”解析然后应用转换函数来避免这一步,但归根结底,我只是在转移需要解决这个问题的地方。

在此之后,我想将生成的 HList 转换为元组并执行 mapN 以创建案例类的新实例

我尝试了几种策略,但总是有一些不起作用。我发现this specific answer 很有帮助,我在下面进行了改编。

此刻我不断得到

could not find implicit value for parameter tupler

当我尝试将生成的 HList 转换为元组时。我注意到我生成的 HList 以 HNil.type 而不是 HNil 终止,这是我所期望的

更新:我在Scastie 上添加了更完整的代码,包含更多的 cmets 和数据。

版本:

  • SBT 1.2.8
  • Scala 2.13.0
  • 无形 2.3.3
import CsvDefinitions._
import CsvTypeParser._
import CsvConverter._
import cats.data._
import cats.implicits._
import scala.util.Try
import shapeless._
import shapeless.ops.hlist.RightFolder
import shapeless.syntax.std.tuple._

object CsvDefinitions {
  type CsvRow = List[String]
  type CsvValidated[A] = ValidatedNec[Throwable, A]
  // A parser is a function that given a row returns a validted result
  type CsvRowParser[A] = Kleisli[CsvValidated, CsvRow, A]
}

trait CsvTypeParser[A] {
  def parseCell(cell: String): CsvValidated[A]
}

object CsvTypeParser {
  def parse[A](extract: CsvRow => String)(implicit parser: CsvTypeParser[A]): CsvRowParser[A] =
    Kleisli { row =>
      val extracted = Try(extract(row)).toEither.toValidatedNec
      val parsed = parser.parseCell _
      (extracted) andThen parsed
    }
  def apply[A](f: String => A): CsvTypeParser[A] = new CsvTypeParser[A] {
    def parseCell(cell: String): CsvValidated[A] = Try(f(cell)).toEither.toValidatedNec
  }
  implicit val stringParser: CsvTypeParser[String] = CsvTypeParser[String] {
    _.trim
  }
  implicit val intParser: CsvTypeParser[Int] = CsvTypeParser[Int] {
    _.trim.toInt
  }
  implicit val doubleParser: CsvTypeParser[Double] = CsvTypeParser[Double] {
    _.trim.toDouble
  }
}

object CsvConverter {
  // The following has been adapted from https://stackoverflow.com/a/25316124:
  private object ApplyRow extends Poly2 {
    // The "trick" here is to pass the row as the initial value of the fold and carry it along
    // during the computation. Inside the computation we apply a parser using row as parameter and
    // then we append it to the accumulator.
    implicit def aDefault[T, V <: HList] = at[CsvRowParser[T], (CsvRow, V)] {
      case (rowParser, (row, accumulator)) => (row, rowParser(row) :: accumulator)
    }
  }

  def convertRowGeneric[
    HP <: HList,    // HList Parsers
    HV <: HList](   // HList Validated
      input: HP,
      row: CsvRow)(
      implicit
        // I tried to use the RightFolder.Aux reading https://stackoverflow.com/a/54417915
        folder: RightFolder.Aux[
          HP,                   // Input type
          (CsvRow, HNil.type),  // Initial value of accumulator
          ApplyRow.type,        // Polymorphic function
          (CsvRow, HV)          // Result type
        ]
      ): HV = {
    input.foldRight((row, HNil))(ApplyRow)._2
  }
}

// Case class containing the final result of the conversion
case class FinalData(id: Int, name: String, score: Double)

object Main extends App {

  // Definition of parsers and how they obtain the value to parse
  val parsers =
    parse[Int   ](r => r(0)) ::          // Extract field 0 and convert it to Int
    parse[String](r => r(1)+" "+r(2)) :: // Get field 1 and 2 together
    parse[Double](r => r(3).trim) ::     // Trim field 3 before converting to double
    HNil

  // One line in the CSV file
  val row = List("123", "Matt", "Smith", "45.67")

  val validated = convertRowGeneric(parsers, row)
  println(validated)  

  // Could not find implicit value for parameter tupler
  // The "validated" HList terminates with HNil.type
  val finalData = validated
    .tupled
    .mapN(FinalData)
  println(finalData)
}

【问题讨论】:

  • 无法重现您的编译错误scastie.scala-lang.org/iHhlPGuKQgqJd5UmqyyAiA 代码编译但由于parse 而抛出NotImplementedError。顺便说一句,convertRowGeneric(parsers, row) 中的row 是什么?下次请不要删除导入。
  • 谢谢,抱歉,我在深夜发布了这个问题。我使 Scastie 上的代码正常工作,添加了正确的导入和示例数据。奇怪的是,现在它可以工作了,但它不在我的项目中
  • 你能把你的项目(不能编译)发布到 github 上吗?
  • 您带有convertRowGeneric 的新代码将为我编译。试试sbt clean compile
  • 我发现了一个类型约束的问题,我没有把它放在这里,因为我认为它是装饰性的。在 Scasti 上,这是它第一次奏效!现在我遇到了一个新问题,因为生成的 HList 以 HNil.type 终止,我无法将其转换为元组

标签: scala shapeless scala-cats


【解决方案1】:

修复convertRowGeneric(将类型HNil.type替换为HNil,将值HNil替换为归属HNil: HNil

def convertRowGeneric[
  HP <: HList,    // HList Parsers
  HV <: HList](   // HList Validated
                  input: HP,
                  row: CsvRow)(
                implicit
                folder: RightFolder.Aux[
                  HP,                   // Input type
                  (CsvRow, HNil),  // Initial value of accumulator
                  ApplyRow.type,        // Polymorphic function
                  (CsvRow, HV)          // Result type
                ]
              ): HV = {
  input.foldRight((row, HNil: HNil))(ApplyRow)._2
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多