gpt4 book ai didi

scala - Shapeless的“惰性”和默认参数导致隐式解析失败

转载 作者:行者123 更新时间:2023-12-01 07:24:28 30 4
gpt4 key购买 nike

我的一个项目使用了混合的Scala功能,这些功能似乎不能很好地融合在一起:

  • 类型类和无形自动类型类实例派生
  • 隐式转换(向具有类型类实例的类型添加有用的语法)
  • 默认参数,因为即使它们通常是一件坏事,但在这里还是太方便了

  • 我遇到的问题是,如果发生以下情况,则类型类实例派生失败:

    没有明确指定
  • 默认参数
  • 无形推导使用Lazy

  • 这是我可以编写以重现此问题的最小代码量:

    Show.scala
    import shapeless._

    trait Show[A] {
    def show(a: A): String
    }

    object Show {
    def from[A](f: A => String): Show[A] = new Show[A] {
    override def show(a: A) = f(a)
    }

    implicit val intShow: Show[Int] = Show.from(_.toString)

    implicit def singletonShow[A](implicit
    sa: Show[A]
    ): Show[A :: HNil] = Show.from {
    case (a :: HNil) => sa.show(a)
    }

    implicit def singletonCaseClassShow[A, H <: HList](implicit
    gen: Generic.Aux[A, H],
    sh: Lazy[Show[H]]
    ): Show[A] = Show.from {
    a => sh.value.show(gen.to(a))
    }
    }

    斯卡拉
    object Run extends App {
    implicit class ShowOps[A](val a: A) extends AnyVal {
    def show(header: String = "> ")(implicit sa: Show[A]): String =
    header + sa.show(a)
    }

    case class Foo(i: Int)

    println(Foo(12).show())
    }

    这无法通过以下错误消息进行编译:
    Run.scala:10: could not find implicit value for parameter sa: Show[Run.Foo]
    [error] println(Foo(12).show())

    编译错误通过以下任一方法解决:
  • 明确将header参数传递给show中的Run.scala
  • 删除Lazy中的隐式Show[H]Show.scala包装器

  • 我必须承认我在这里完全不知所措。我很想了解会发生什么,并且我想知道一种解决方法(如果存在)。

    最佳答案

    简短答案:

    如果将上下文移动到隐式类,则它也可以正常工作。您必须牺牲value类来做到这一点,但是我认为更容易告诉编译器仅具有AShow会被它丰富:

    implicit class Show2Ops[A : Show](a: A) {
    def show2(header: String = "> ") = header + implicitly[Show[A]].show(a)
    }

    println(Foo(12).show2())

    长理论:
    Lazy做一些有趣的技巧,这些技巧很难遵循。您没有特别询问 Lazy在做什么,但是我对此感到很好奇,因为我一直在使用它,但不确定它是如何工作的。所以我看了一下。据我所知,它就像这样。

    您有一个带有递归字段的案例类:
    case class A(first: Int, next: Option[A])

    并假设您在 ShowOption伴侣中还有另一种情况:
    implicit def opt[A](implicit showA: Show[A]): Show[Option[A]] = Show.from {
    case Some(a) => s"Some(${showA.show(a)})"
    case None => "None"
    }

    singletonShow相比,您有一个真实的 HNil情况和一个归纳情况,通常是这样:
    implicit val hnil: Show[HNil] = Show.from(_ => "")
    implicit def hcons[H, T <: HList](implicit
    showH: Show[H],
    showT: Show[T]
    ): Show[H :: T] = Show.from {
    case h :: t => showH(h) + ", " + showT(t) // for example
    }

    让我们将 singletonCaseClassShow重命名为 genericShow,因为它不再仅适用于单例。

    现在假设您在 Lazy中没有 genericShow。当您尝试调用 Show[A]时,编译器将转到:
  • genericShow[A],带有针对Show[A]的开放式隐式搜索
  • hcons[Int :: Option[A] :: HNil],具有对Show[A]Show[Int :: Option[A] :: HNil的开放式隐式搜索
  • intShow,使用开放式隐式搜索Show[A]Show[Int]Show[Option[A] :: HNil]
  • hcons[Option[A] :: HNil],具有对Show[A]Show[Option[A] :: HNil]的开放式隐式搜索
  • opt[A],使用开放式隐式搜索Show[A]Show[Option[A]]Show[Option[A] :: HNil]
  • genericShow[A],使用开放式隐式搜索Show[A]Show[Option[A]]Show[Option[A] :: HNil]

  • 现在很明显,这是有问题的,因为它会回到第二位并再次发生,永远不会取得任何进展。
    Lazy如何克服此问题的方法是在编译器尝试实现其隐式实例时进入宏。因此,当您在 implicit showH: Lazy[Show[H]]中使用 hcons而不是 Show[H]时,编译器会转到该宏以查找 Lazy[Show[H]],而不是停留在隐式 Show情况下。

    宏检查打开的隐式对象(哪些宏可以有帮助地访问),并进入其自己的隐式分辨率算法,该算法始终完全解析打开的隐式对象,然后继续查找 T的隐式实例(对于 Lazy[T])。如果要解决已经打开的隐式,它将替换一个伪树(本质上是告诉编译器“我明白了,不用担心”),该伪树跟踪打结的依赖项,以便其余解析得以完成。最后,它清理了虚拟树(我不太清楚这是如何工作的;那里有很多代码,而且非常复杂!)

    那么,为什么 Lazy似乎弄乱了您的默认参数情况?我认为这是几件事的融合(仅是一个假设):
  • 使用您的原始ShowOps,在一个值上调用.show会使该值隐式包装在ShowOps[A]中。什么是A?是FooAnyRefAny吗?它将是唯一的单一类型吗?这还不是很清楚,因为那时A上没有任何限制,而Scala不知道您对.show的调用实际上会对其进行限制(由于上下文的限制)。
  • 如果不使用Lazy,则可以正常运行,因为如果Scala选择了错误的A.show没有进行类型检查,它将意识到它的错误并从选择的A中退出。
  • 使用Lazy,还有许多其他逻辑,Scala欺骗了Scala以为它选择的任何A都很好。但是,当需要关闭循环时,它就无法解决,到那时,为时已晚。
  • 某种程度上,未指定的默认参数会影响Scala在A中选择ShowOps[A]的初始选择。
  • 关于scala - Shapeless的“惰性”和默认参数导致隐式解析失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42318018/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com