1

最终编辑:所有极端情况都已解决,唯一的问题是我必须Encoder.encodeTraversableOnce从 Circe 复制一个私有方法才能使Encoder's 工作。我还必须更改MyCollection为使用TraversableOnce,而不仅仅是Traversable(这是因为Encoder' 仅适用TraversableOncewhereDecoderTraversable. 15 .

小提琴可以在这里找到https://scalafiddle.io/sf/F5Qo8cn/8

基本上我正在尝试对集合类型进行抽象,这是在包含可遍历集合的模型的上下文中,即假设我们有以下内容

case class MyCollection[C[A] <: Traversable[A]](stuff: C[String])

这允许我们MyCollection使用特定的集合类型进行实例化,即

val innerV = MyCollection(Vector("a"))
val innerL = MyCollection(List("b"))

MyCollection也将碰巧有一个具体的类型,所以当我们访问该.stuff方法时,它将返回我们用来创建它的类型(即在它的情况下与它innerVVectorwhere 一样)innerLList

由于这是 web 框架的上下文,MyCollection恰好代表一些 JSON,所以使用 Circe 0.9.1 我们可以编写如下解码器

object MyCollection {

  implicit def decoder[C[A] <: Traversable[A]]: Decoder[MyCollection[C]] = {
    new Decoder[MyCollection[C]] {
      override def apply(c: HCursor) = {
        c.downField("stuff").as[C[String]](Decoder.decodeTraversable[String, C](
          implicitly,
          implicitly
        ))
      }.map((x: C[String]) => MyCollection.apply(x))
    }
  }
}

请注意,我们implicit显式调用参数以及手动编写解码器,以便我们可以帮助跟踪隐式问题所在。这个想法是我们可以case class用我们想要的任何集合类型来一般地实例化 a,即

def getMyCollection[C[A] <: Traversable[A]]: MyCollection[C] = {
  val jsonString = """{ "stuff": ["a","b"] }"""
  val json = io.circe.parser.parse(jsonString).right.get
  json.as[MyCollection[C]].right.get
}

def asVector: MyCollection[Vector] = getMyCollection[Vector]
def asList: MyCollection[List] = getMyCollection[List]

问题是我得到了一个不同的隐式扩展,特别是在这条线上

c.downField("stuff").as[C[String]](Decoder.decodeTraversable[String, C](
  implicitly,
  implicitly // <- error happens here, this is a CBF implicit
))

我们得到的错误是

ScalaFiddle.scala:19:错误:不明确的隐式值:类型为 => generic.this.CanBuildFrom[String,scala.this.Char,String] 的模块类 Predef 中的 getter StringCanBuildFrom 和类型为 [ 的模块类 Predef 中的方法 $conforms A]=> $less$colon$less[A,A] 隐式匹配预期类型 T ^

有谁知道是什么导致了这个问题

4

2 回答 2

2

你的上限太宽松了:在方法的主体内部,编译器除了它是 a 之外C一无所知,因此它不能自动证明存在 的实例。CTraversable[A]CanBuildFrom[Nothing, A, C[A]]

简单的解决方法是CanBuildFrom[Nothing, A, C[A]]从外部提供,因为这些东西很容易在使用站点上生成(因为它显然可以用于像Listand之类的具体实现Vector):

// Start writing your ScalaFiddle code here
import io.circe._
import io.circe.syntax._
import scala.collection.generic.CanBuildFrom

case class MyCollection[C[A] <: Traversable[A]](stuff: C[String])


val innerV = MyCollection(Vector("a")).stuff
val innerL = MyCollection(List("b")).stuff

object MyCollection {

  implicit def decoder[C[A] <: Traversable[A]]
    (implicit cbf: CanBuildFrom[Nothing, String, C[String]])
  : Decoder[MyCollection[C]] = {
    new Decoder[MyCollection[C]] {
      override def apply(c: HCursor) = {
        c.downField("stuff").as[C[String]](Decoder.decodeTraversable[String, C](
          implicitly,
          // this thing cannot be generated 
          // if you know nothing about `C` except
          // that it is a `Traversable[A]`
          cbf
        ))
      }.map((x: C[String]) => MyCollection.apply(x))
    }
  }
}

def getMyCollection[C[A] <: Traversable[A]]
  (implicit cbf: CanBuildFrom[Nothing, String, C[String]])
: MyCollection[C] = {
  val jsonString = """{ "stuff": ["a","b"] }"""
  val json = io.circe.parser.parse(jsonString).right.get
  json.as[MyCollection[C]].right.get
}

// cbf is supplied by compiler, it is trivial to
// generate here, because you know that you can do it
// for lists and vectors
def asVector: MyCollection[Vector] = getMyCollection[Vector] 
def asList: MyCollection[List] = getMyCollection[List] 

println(asVector)
println(asList)

编辑:正如@OlegPyzhcov 所指出的那样,下面的提案不起作用,因为我们没有GenTraversable可以调用的实例companion。我把它留在这里,以防我突然想起我在想什么。

我能想到的另一个解决方案是收紧 to 的上限 GenTraversable[A],然后越过companionto ,并使用该方法GenericCompanion构建所需的。CanBuildFrom[Nothing, String, C[String]]newBuilder

权衡是:这会将 bound 更改 Traversable[A]为更紧密GenTraversable[A],但是您可以放弃烦人的cbf-implicit。

于 2018-02-09T20:19:06.900 回答
1

注意:Scala 编译器选项-Xlog-implicits有时可以帮助您了解正在发生的事情(尽管这种情况奇怪!)。

您没有范围内的隐式 CBF。C[_]您需要从已知具体类型的顶层传递它。所以解决方法是:

implicit def decoder[C[A] <: Traversable[A]](implicit cbf: CanBuildFrom[Nothing, String, C[String]]): Decoder[MyCollection[C]]

如果你删除implicitly语句,你会得到一个不同的错误,它应该暗示你错过了它。

Cannot construct a collection of type C[String] with elements of type String based on a collection of type Nothing.

not enough arguments for method decodeTraversable: (implicit decodeA: io.circe.Decoder[String], implicit cbf: scala.collection.generic.CanBuildFrom[Nothing,String,C[String]])io.circe.Decoder[C[String]].
Unspecified value parameter cbf.

下一部分主要是假设,因此请持保留态度。也许一些 Scala 编译器黑客会纠正我。

触发错误的原因在于错误的expected type T来源。T不作为类型参数存在Decoder,也不存在于 中CanBuildFrom。但它在 scala.Predef 中使用:

 @inline def implicitly[T](implicit e: T) = e

因此,当您使用 时implicitly,编译器可以尝试两件事:

  • CanBuildFrom发现实例Decoder.decodeTraversable
  • (较低优先级)发现隐式可用的东西,然后尝试找到隐式转换以获得CanBuildFrom

第一步失败(没有CanBuildFrom)。但这还没有结束。所以编译器试图找到implicit e: T对类型 T 没有任何限制的东西。它找到了多个东西:

  • StringCanBuildFrom
  • $conforms

因此,一旦它发现了不同的隐式,它会立即退出,给你提供的错误信息并不有用。

通过修复我建议使用implicitly或不使用没有区别,因为它在第一步完成。

于 2018-02-09T20:27:13.990 回答