懒惰的无形调用值使StackOverflowError成为可能

时间:2016-10-24 14:55:07

标签: scala implicit shapeless

通过这个有用的guide

学习无形

我试图在this

中合并我从this exercice学到的知识

结果是这段代码:

import shapeless._
object GenericV3 {


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

  def createEncoder[A](cols: Int)(func: A => List[String]): CsvEncoder[A] =
    new CsvEncoder[A] {
      val width = cols
      def encode(value: A): List[String] =
        func(value)
    }

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

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

  implicit val booleanEncoder: CsvEncoder[Boolean] =
    createEncoder(1)(bool => List(if(bool) "cone" else "glass"))

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

  import shapeless.{HList, HNil, ::}

  implicit val hnilEncoder: CsvEncoder[HNil] =
    createEncoder(0)(hnil => Nil)

  implicit def hlistEncoder[H, T <: HList](
                                            implicit
                                            hEncoder: Lazy[CsvEncoder[H]],
                                            tEncoder: CsvEncoder[T]
                                          ): CsvEncoder[H :: T] =
    createEncoder(hEncoder.value.width + tEncoder.width) {
      case h :: t =>
        hEncoder.value.encode(h) ++ tEncoder.encode(t)
    }


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

  implicit val cnilEncoder: CsvEncoder[CNil] =
    createEncoder(0) { cnil =>
      throw new Exception("The impossible has happened!")
    }

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


  import shapeless.Generic

  implicit def genericEncoder[A, R](
                                     implicit
                                     gen: Generic.Aux[A, R],
                                     lEncoder: Lazy[CsvEncoder[R]]
                                   ): CsvEncoder[A] =
    createEncoder(lEncoder.value.width) { value =>
      lEncoder.value.encode(gen.to(value))
    }


  def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
    values.map(encoder.encode).map(_.mkString(",")).mkString("\n")
}

使用密封的案例类调用GenericV3.writeCsv时,案例类包含密封的案例类本身。

指南中的示例:

sealed trait Tree[A]
    final case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
    final case class Leaf[A](value: A) extends Tree[A]

我在运行时遇到了StackOverflowError。 例子

GenericV3.writeCsv[Tree[String]](List(Branch(Leaf("toto"),Leaf("titi"))))

产生错误是因为我在匹配Inl和Inr之前调用hEncoder.value.width

我试着看看它是如何在其他库中完成的。 我看到this看起来一样。

所以我想我错过了什么。

ps:我要感谢这位guide的作者非常有帮助

0 个答案:

没有答案