Shapeless FoldRight 后无法将 HList 转换为元组
Cannot convert HList to tuple after Shapeless FoldRight
我正在尝试使用 Scala 创建一个 CSV 文件解析器到一个案例中 class,我正在尝试使用 Shapeless 使其通用。
我希望我的解析器允许用户指定提取函数 extract: CsvRow => String
而不是针对特定字段类型具有 1 对 1 对应关系和类型 classes 因为文件我' m 解析采用不同的格式,适用于一个文件的解析操作不适用于另一个文件。
我想如果我可以通过首先进行 "dumb" 解析然后应用转换函数来避免这一步,但在一天结束时我只是转移到需要解决这个问题的地方。
在此之后,我想将生成的 HList 转换为元组并执行 mapN 以创建案例的新实例 class
我尝试了几种策略,但总有一些方法不起作用。我发现非常有帮助 this specific answer 我在下面进行了改编。
目前我不断得到
could not find implicit value for parameter tupler
当我尝试将生成的 HList 转换为元组时。我注意到我生成的 HList 以 HNil.type
而不是我期望的 HNil
终止
更新: 我在 Scastie 上添加了更完整的代码,其中包含更多评论和数据。
版本:
- 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
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
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)
}
修复 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
}
我正在尝试使用 Scala 创建一个 CSV 文件解析器到一个案例中 class,我正在尝试使用 Shapeless 使其通用。
我希望我的解析器允许用户指定提取函数 extract: CsvRow => String
而不是针对特定字段类型具有 1 对 1 对应关系和类型 classes 因为文件我' m 解析采用不同的格式,适用于一个文件的解析操作不适用于另一个文件。
我想如果我可以通过首先进行 "dumb" 解析然后应用转换函数来避免这一步,但在一天结束时我只是转移到需要解决这个问题的地方。
在此之后,我想将生成的 HList 转换为元组并执行 mapN 以创建案例的新实例 class
我尝试了几种策略,但总有一些方法不起作用。我发现非常有帮助 this specific answer 我在下面进行了改编。
目前我不断得到
could not find implicit value for parameter tupler
当我尝试将生成的 HList 转换为元组时。我注意到我生成的 HList 以 HNil.type
而不是我期望的 HNil
终止
更新: 我在 Scastie 上添加了更完整的代码,其中包含更多评论和数据。
版本:
- 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
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
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)
}
修复 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
}