softshipper
softshipper

Reputation: 34071

could not find implicit value for parameter enc: CsvEncoder[Shape]

I am trying to understand coproduct of shapeless and have following example, that does not work:

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

  }

}

The compiler complains:

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

Do I have create an instance of CsvEncoder for Shape? Shapeless should care it for me, or?

Upvotes: 0

Views: 506

Answers (1)

Ven
Ven

Reputation: 19039

I'd recommend re/reading The Type Astronaut's Guide to Shapeless Book, it really is an amazing book. It seems the code you have posted is partly from there.


The CSV Encoder example uses Employee, and writes the CsvEncoder by hand (page 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"
      )
  }

If you do not want to have to write all this by hand, you're gonna need a bit more boilerplate.


This is described in 3.2.1 of the book, around page 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)
  }

We have the recursion case, hlistEncoder (that returns CsvEncoder[H :: T]), that takes the head and the tail, and the base case encoder, hnilEncoder (that returns CsvEncoder[HNil], because a HList always has a HNil at its end, like every other linked list).

This, however, is enough to CSV-encode any HList, but a Rectangle isn't a HList! You need to use a Generic to convert back-and-forth. This is explained in 3.2.2 of the book, right after the previous part (I converted the code to use Generic.Aux, which is one of the most important tools of shapeless):

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

The gen.to converts a (say Rectangle) to an HList (so, Double :: Double :: HNil, and encodes that. It also converts Shape to Rectangle :+: Circle :+: CNil, so your Main works as-is :).

Upvotes: 3

Related Questions