scala – Shapeless’Lazy和默认参数导致隐式解析失败
我的一个项目使用
scala功能混合,看起来不能很好地混合在一起:
>类型类和无形自动类型类实例派生 我遇到的问题是类型类实例派生失败,如果: >未明确指定默认参数 这是我可以编写的用于重现问题的最小代码量: 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)) } } Run.scala 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参数以在Run.scala中显示 我必须承认我在这里完全失败了.我很想知道会发生什么,如果有的话,我很想知道解决方法. 解决方法
简短回答:
如果将绑定的上下文移动到隐式类,它也可以正常工作.你必须牺牲价值类才能做到这一点,但我认为,事先告诉编译器,只有拥有Show的As会因此而变得更加清晰: implicit class Show2Ops[A : Show](a: A) { def show2(header: String = "> ") = header + implicitly[Show[A]].show(a) } println(Foo(12).show2()) 长理论: 懒惰做了一些有趣的技巧,很难遵循.你并没有特别询问Lazy在做什么,但我很好奇,因为我一直使用它而不确定它是如何工作的.所以我看了一下.就像我所知,它就像这样. 你有一个带有递归字段的case类: case class A(first: Int,next: Option[A]) 并假设您在Show的同伴选项中有另一个案例: 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,因为它不仅仅适用于单身人士. 现在让我们假设你在genericShow中没有Lazy.当你试图召唤一个Show [A]时,编译器会转到: > genericShow [A]打开隐式搜索Show [A] 现在很明显有一个问题,因为它会回到#2并再次发生,从未取得任何进展. Lazy如何克服这个问题是在编译器尝试实现它的隐式实例时进入宏.因此,当您在hcons中使用隐式showH:Lazy [Show [H]]而不是Show [H]时,编译器会转到该宏来查找Lazy [Show [H]],而不是保留在隐式Show Case中. 宏检查open implicits(哪些宏有助于访问)并进入其自己的隐式解析算法,该算法始终完全解析开放的implicits,然后继续查找T的隐式实例(对于Lazy [T]).如果要解析已经打开的隐式,它会替换一个虚拟树(基本上告诉编译器“我得到了这个,不要担心它”),它跟踪打结的依赖关系,以便其余的解析完成.最后,它清理了虚拟树(我无法弄清楚它是如何工作的;那里有大量令人惊讶的代码而且它非常复杂!) 那么为什么懒惰似乎搞乱你的默认参数情况呢?我认为这是几件事的汇合(只是一个假设): >使用原始的ShowOps,在值上调用.show会导致它隐式包装在ShowOps [A]中.什么是A?会不会是Foo,AnyRef,Any?它会成为一种独特的单一类型吗?它并不完全清楚,因为当时对A没有约束,而Scala不知道你对.show的调用会实际限制它(由于上下文绑定).>如果没有Lazy,这可以解决问题,因为如果Scala选择了错误的A而且.show没有进行类型检查,它将意识到它的错误并退出它所选择的A.>对于Lazy,还有很多其他的逻辑正在进行中,它有点欺骗Scala认为无论A选择什么都没问题.但是当关闭循环的时候,它就没有用了,到那时为止已经太晚了.>不知何故,默认参数未指定会影响Scala在ShowOps [A]中选择A的初始选择. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |