Не удалось найти неявное значение для параметра enc: CsvEncoder[Shape]

Я пытаюсь понять побочный продукт бесформенного и имею следующий пример, который не работает:

import shapeless.{HList, ::, HNil}
import shapeless.Generic
import shapeless.{Coproduct, :+:, CNil, Inl, Inr}

trait CsvEncoder[A] {
  def encode(value: A): List[String]
}


sealed trait Shape

final case class Rectangle(width: Double, height: Double) extends Shape

final case class Circle(radius: Double) extends Shape

object CsvEncoder {

  def createEncoder[A](func: A => List[String]): CsvEncoder[A] = {
    new CsvEncoder[A] {
      override def encode(value: A): List[String] = func(value)
    }
  }

  implicit val booleanEncoder: CsvEncoder[Boolean] =
    createEncoder(value => if (value) List("yes") else List("no"))

  implicit val intEncoder: CsvEncoder[Int] =
    createEncoder(value => List(value.toString))

  implicit val stringEncoder: CsvEncoder[String] =
    createEncoder(value => List(value))

  implicit val doubleEncoder: CsvEncoder[Double] =
    createEncoder(value => List(value.toString))


  def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] = enc

  implicit val cnilEncoder: CsvEncoder[CNil] =
    createEncoder(cnil => throw new Exception("Inconceivable!"))

  implicit def coproductEncoder[H, T <: Coproduct](
                                                    implicit
                                                    hEncoder: CsvEncoder[H],
                                                    tEncoder: CsvEncoder[T]
                                                  ): CsvEncoder[H :+: T] = createEncoder {
    case Inl(h) => hEncoder.encode(h)
    case Inr(t) => tEncoder.encode(t)
  }

  def writeCsv[A](values: List[A])(implicit enc: CsvEncoder[A]): String =
    values.map(value => enc.encode(value).mkString(",")).mkString("\n")


}

object Main {


  def main(args: Array[String]) {

    println("----------------------------------------------------------")
    val shapes: List[Shape] = List(
      Rectangle(3.0, 4.0),
      Circle(1.0)
    )
    println(CsvEncoder.writeCsv(shapes))

  }

}

Компилятор жалуется:

Error:(162, 32) could not find implicit value for parameter enc: CsvEncoder[Shape]
    println(CsvEncoder.writeCsv(shapes))
Error:(162, 32) not enough arguments for method writeCsv: (implicit enc: CsvEncoder[Shape])String.
Unspecified value parameter enc.
    println(CsvEncoder.writeCsv(shapes))

Должен ли я создать экземпляр CsvEncoder за Shape? Бесформенный должен заботиться обо мне, или?

1 ответ

Решение

Я бы рекомендовал перечитать "Руководство астронавта по бесформенной книге", это действительно удивительная книга. Кажется, код, который вы разместили, частично оттуда.


Пример CSV Encoder использует Employeeи пишет CsvEncoder от руки (стр. 23):

implicit val iceCreamEncoder: CsvEncoder[IceCream] =
  new CsvEncoder[IceCream] {
    def encode(i: IceCream): List[String] =
      List(
        i.name,
        i.numCherries.toString,
        if(i.inCone) "yes" else "no"
      )
  }

Если вы не хотите писать все это от руки, вам понадобится немного больше шаблонов.


Это описано в 3.2.1 книги, на странице 27:

import shapeless.{HList, ::, HNil}
implicit val hnilEncoder: CsvEncoder[HNil] =
  createEncoder(hnil => Nil)
implicit def hlistEncoder[H, T <: HList](
  implicit
  hEncoder: CsvEncoder[H],
  tEncoder: CsvEncoder[T]
): CsvEncoder[H :: T] =
  createEncoder {
    case h :: t =>
      hEncoder.encode(h) ++ tEncoder.encode(t)
  }

У нас есть случай рекурсии, hlistEncoder (что возвращает CsvEncoder[H :: T]), который берет голову и хвост, и кодировщик базового случая, hnilEncoder (что возвращает CsvEncoder[HNil]потому что HList всегда имеет HNil в конце, как и любой другой связанный список).

Этого, однако, достаточно для CSV-кодирования любого HList, но Rectangle не является HList! Вам нужно использовать Generic конвертировать туда-сюда. Это объясняется в 3.2.2 книги, сразу после предыдущей части (я преобразовал код для использования Generic.Aux, который является одним из важнейших инструментов бесформенного):

implicit def genericEncoder[A, R](
  implicit
  gen: Generic.Aux[A, R],
  enc: CsvEncoder[R]
): CsvEncoder[A] =
  createEncoder(a => enc.encode(gen.to(a)))

gen.to новообращенные a (сказать Rectangle) в HList (так, Double :: Double :: HNilи кодирует это. Это также преобразует Shape в Rectangle :+: Circle :+: CNil, Так что ваши Main работает как есть:).

Другие вопросы по тегам