在Traversable上抽象时发散的隐式扩展

时间:2018-02-09 19:25:00

标签: scala collections implicit scala-cats circe

最终编辑:所有角落案例都已解决,唯一的问题是我必须从Circe复制私有/me方法才能使Encoder.encodeTraversableOnce正常工作。我还必须更改Encoder以使用MyCollection而不仅仅是TraversableOnce(这是因为Traversable仅适用于Encoder TraversableOnceDecoder一起使用。演示所有案例的小提琴可以在https://scalafiddle.io/sf/F5Qo8cn/15找到。

小提琴可以在https://scalafiddle.io/sf/F5Qo8cn/8

找到

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

Traversable

这允许我们使用特定的集合类型实例化case class MyCollection[C[A] <: Traversable[A]](stuff: C[String]) ,即

MyCollection

val innerV = MyCollection(Vector("a")) val innerL = MyCollection(List("b")) 也恰好具有一个具体类型,因此当我们访问MyCollection方法时,它将返回我们用来创建它的类型(即.stuff的情况其innerVVector的{​​{1}}

相同

由于这是Web框架的上下文,innerL碰巧代表了一些JSON,因此使用Circe 0.9.1我们可以编写如下解码器

List

请注意,我们会明确调用MyCollection参数以及手动编写解码器,以便我们可以帮助跟踪隐含问题的位置。我们的想法是,我们可以通常使用我们想要的任何集合类型来实例化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

我们得到的错误是

  

ScalaFiddle.scala:19:错误:含糊不清的隐含值:两者都是getter   StringCanBuildFrom在模块类Predef中的type =&gt;   generic.this.CanBuildFrom [String,scala.this.Char,String]和方法   $符合模块类Predef的类型[A] =&gt; $ $少结肠$更少[A,A]   匹配预期类型T.               隐式               ^

有谁知道造成这个问题的原因

2 个答案:

答案 0 :(得分:2)

C的上限过于松散:在方法体内,编译器对C一无所知,只是它是Traversable[A],因此无法自动证明有一个CanBuildFrom[Nothing, A, C[A]]的实例。

简单的解决方法是从外部提供CanBuildFrom[Nothing, A, C[A]],因为这些东西很容易在使用网站上生成(因为它显然可以用于List和{等具体实现{1}}):

Vector

编辑:以下提案无效,正如@OlegPyzhcov所指出的那样,因为我们没有// 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) 的实例可以调用GenTraversable。我会留在这里,以防我突然想起我在想什么。

  

我能想到的另一个解决方案是收紧上限   companion,然后查看GenTraversable[A]   companion,并使用GenericCompanion方法构建所需的CanBuildFrom[Nothing, String, C[String]]

     

权衡将是:这将改变界限   newBuilder要更紧Traversable[A],但你可以   放弃恼人的GenTraversable[A] - 隐含。

答案 1 :(得分:1)

注意:Scala编译器选项C[_]有时可以帮助您了解发生了什么(虽然这种情况 非常奇怪!)。

您在范围内没有隐含的CBF。您需要从顶级传递它,其中已知具体类型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. 语句,则会收到一个不同的错误,该错误应该暗示您错过了它。

expected type T

接下来的部分主要是假设,所以请耐心等待。也许一些Scala编译器黑客会纠正我。

触发错误的答案取决于T的来源。 Decoder中的CanBuildFrom不在 @inline def implicitly[T](implicit e: T) = e 中,也不在implicitly中。但它用于scala.Predef:

CanBuildFrom

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

  • 根据CanBuildFrom
  • 发现CanBuildFrom的实例
  • (较低优先级)发现隐式可用的某些内容,然后尝试查找隐式转换以获取implicit e: T

第一步失败(没有StringCanBuildFrom)。但它还没有结束。所以编译器试图找到$conforms而不对类型T有任何约束。它找到了多个东西:

  • implicitly
  • {{1}}

因此,一旦发现有不同的含义,它会立即退出,给您的错误信息不足。

使用修复我建议使用{{1}}或不同意,因为它在第一步结束。