【问题标题】:Divergent implicit expansion when abstracting over Traversable对 Traversable 进行抽象时的发散隐式扩展
【发布时间】:2018-07-20 15:14:12
【问题描述】:

最终编辑:所有极端情况都已解决,唯一的问题是我必须从 Circe 复制私有 Encoder.encodeTraversableOnce 方法才能使 Encoder 正常工作。我还必须将MyCollection 更改为使用TraversableOnce 而不仅仅是Traversable(这是因为Encoder 只能与TraversableOnce 一起使用,而DecoderTraversable 一起使用。演示所有案例都可以在这里找到https://scalafiddle.io/sf/F5Qo8cn/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 方法时,它将返回我们用来创建它的类型(即,在innerV 的情况下,它的Vector 是与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,即

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: 错误: 模糊的隐含值: 两个 getter 类型为 Predef 的模块类中的 StringCanBuildFrom => generic.this.CanBuildFrom[String,scala.this.Char,String] 和方法 [A]=> $less$colon$less[A,A] 类型的模块类 Predef 中的 $conforms 匹配预期类型 T 含蓄地 ^

有谁知道这个问题的原因

【问题讨论】:

    标签: scala collections implicit scala-cats circe


    【解决方案1】:

    C 的上限太宽松:在方法体内,编译器对 C 一无所知,除了它是一个 Traversable[A],因此它无法自动证明存在CanBuildFrom[Nothing, A, C[A]].

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

    // 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 所指出的那样,下面的提议不起作用,因为我们没有可以调用companionGenTraversable 实例。我把它留在这里,以防我突然想起我在想什么。

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

    权衡是:这将改变从 Traversable[A] 更紧GenTraversable[A],但你可以 放弃烦人的cbf-implicit。

    【讨论】:

    • 第二种解决方案的问题是解码JSON时,还没有实例可以调用companion
    • @OlegPyzhcov 哦……确实。我不知何故陷入了数据结构重写上下文中,其中一个可遍历对象必须转换为另一个相同类型但具有不同元素的可遍历对象。当然,如果没有 Traversable 实例开始,这没有任何意义。谢谢,马上解决。
    【解决方案2】:

    注意: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 的来源。 TDecoderCanBuildFrom 中都不作为类型参数存在。但在scala.Predef中使用:

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

    所以当你使用implicitly 时,编译器可以尝试两件事:

    • 按照Decoder.decodeTraversable 的指示发现CanBuildFrom 的实例
    • (较低优先级)发现 something 隐式可用,然后尝试找到隐式转换以获取 CanBuildFrom

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

    • StringCanBuildFrom
    • $conforms

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

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

    【讨论】:

    • 谢谢,这绝对是解决我问题的一个非常直接的方法。我想我唯一的抱怨是getMyCollection 现在必须知道MyCollection 的内部类型并在各处传递相关的隐式(implicit cbf: CanBuildFrom[Nothing, String, C[String]])。有没有更好的解决方案,比如使用猫类型类?即也许使用具有更好推理能力的case class MyCollection[F: Foldable[_]](stuff: F[String])
    • @mdedetrich 我认为没有。您可以使用Applicative[C](用于pure)和Monoid[C[String]]MonoidK[C](用于连接和空箱)的组合,但circe不直接支持它,所以你必须这样做手工。
    • 很抱歉碰到这个问题,但我实际上意识到上述解决方案不适用于参数化类型,即参见scalafiddle.io/sf/F5Qo8cn/9
    • @mdedetrich 您忘记将编码器和解码器声明为隐式。此外,逆变不适用于字段,可以是协变的,也可以是不变的。
    • 好的,所以我终于弄清楚了一切,抱歉我之前粘贴的小提琴不正确。我实际上遇到了递归数据结构的问题。我设法让一切正常工作,唯一的问题是我基本上必须从 Circe 复制一个私人编码器。这里的小提琴更不言自明scalafiddle.io/sf/F5Qo8cn/15
    猜你喜欢
    • 2017-12-30
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 2014-01-01
    • 2011-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多