代码之家  ›  专栏  ›  技术社区  ›  mdedetrich

可遍历抽象时的发散隐式展开

  •  1
  • mdedetrich  · 技术社区  · 7 年前

    最终编辑:所有角落的案子都已解决,唯一的问题是我必须复制一份私人文件 Encoder.encodeTraversableOnce 方法从Circe获取 Encoder 正在工作。我也不得不改变 MyCollection 使用 TraversableOnce 而不仅仅是 Traversable (这是因为 编码器 's only work with 可遍历一次 其中作为 Decoder 使用 可遍历的 . 展示所有案例的提琴可以在这里找到 https://scalafiddle.io/sf/F5Qo8cn/15 .

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

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

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

    这允许我们实例化 我收集 具有特定集合类型,即。

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

    我收集 也会碰巧有一个具体的类型,所以当我们访问 .stuff 方法,它将返回我们用于在其上创建的类型(即 innerV 它的 Vector 何处与 innerL 它的 List )

    由于这是web框架的上下文, 我收集 碰巧表示一些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; 通用的这CanBuildFrom[字符串,scala.this.Char,String]和方法 $符合类型为[A]=>的模块类Predef$减去$冒号$减去[A,A] 匹配预期类型T 含蓄地 ^

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

    2 回复  |  直到 7 年前
        1
  •  2
  •   Andrey Tyukin    7 年前

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

    简单的解决方法是提供 CanBuildFrom[无,A,C[A]] 从外部来看,因为这些东西很容易在使用站点上生成(因为它显然可以用于以下具体实现 List 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 . 我会把它留在这里,以防我突然想起我在想什么。

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

    折衷是:这将改变 可遍历的,可遍历的 至更紧 GenTraversable[一] ,但你可以 放下讨厌的东西 cbf -隐式。

        2
  •  1
  •   Oleg Pyzhcov    7 年前

    注意: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
    

    所以当你使用 含蓄地 ,编译器可以尝试两种方法:

    • 发现的实例 CanBuildFrom 根据指示 Decoder.decodeTraversable
    • (低优先级)发现 某物 隐式可用,然后尝试查找隐式转换以获取 CanBuildFrom

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

    • StringCanBuildFrom
    • $conforms

    所以,一旦它发现了分歧的含义,它就会立即退出,给您提供的错误消息并没有什么帮助。

    对于修复,我建议使用 含蓄地 或者不做没有什么区别,因为它在第一步就完成了。