我的一个项目使用scala功能混合,看起来不能很好地混合在一起:
我遇到的问题是类型类实例派生失败,如果:
Lazy这是我可以编写的用于重现问题的最小代码量:
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 Code Online (Sandbox Code Playgroud)
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 Code Online (Sandbox Code Playgroud)
无法使用以下错误消息进行编译:
Run.scala:10: could not find implicit value for parameter sa: Show[Run.Foo]
[error] println(Foo(12).show())
Run Code Online (Sandbox Code Playgroud)
编译错误由以下任一方法修复:
header参数传递给showinRun.scalaLazy包装器移除到隐含Show[H]的Show.scala我必须承认我在这里完全失败了.我很想知道会发生什么,如果有的话,我很想知道解决方法.
Jer*_*emy 13
简短回答:
如果将绑定的上下文移动到隐式类,它也可以正常工作.你必须牺牲价值类才能做到这一点,但我认为在前面告诉编译器,只有A具有Show遗嘱的s 才能更加丰富:
implicit class Show2Ops[A : Show](a: A) {
def show2(header: String = "> ") = header + implicitly[Show[A]].show(a)
}
println(Foo(12).show2())
Run Code Online (Sandbox Code Playgroud)
长理论:
Lazy做一些有趣的技巧,很难遵循.你没有具体询问Lazy正在做什么,但我很好奇,因为我一直使用它而不确定它是如何工作的.所以我看了一下.就像我所知,它就像这样.
你有一个带有递归字段的case类:
case class A(first: Int, next: Option[A])
Run Code Online (Sandbox Code Playgroud)
并假设你的另一个案例Show是Option:
implicit def opt[A](implicit showA: Show[A]): Show[Option[A]] = Show.from {
case Some(a) => s"Some(${showA.show(a)})"
case None => "None"
}
Run Code Online (Sandbox Code Playgroud)
而不是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
}
Run Code Online (Sandbox Code Playgroud)
让我们重命名singletonCaseClassShow,genericShow因为它不仅仅是单身人士了.
现在让我们说你没有Lazy那里genericShow.当你试图召唤一个时Show[A],编译器会转到:
genericShow[A] 使用开放隐式搜索 Show[A]hcons[Int :: Option[A] :: HNil]与开放式隐含搜索Show[A]和Show[Int :: Option[A] :: HNilintShow用隐式搜索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]现在很明显有一个问题,因为它会回到#2并再次发生,从未取得任何进展.
如何Lazy克服这一点是通过在编译器尝试实现它的隐式实例时进入宏.因此,当您使用implicit showH: Lazy[Show[H]]in hcons而不是just时Show[H],编译器会转到该宏来查找Lazy[Show[H]]而不是保留在您的隐式Show情况中.
宏检查open implicits(哪些宏有助于访问)并进入其自己的隐式解析算法,该算法在继续查找T(for Lazy[T])的隐式实例之前始终完全解析开放的implicits .如果要解析已经打开的隐式,它会替换一个虚拟树(基本上告诉编译器"我得到了这个,不要担心它"),它跟踪打结的依赖关系,以便其余的解析完成.最后,它清理了虚拟树(我无法弄清楚它是如何工作的;那里有大量令人惊讶的代码而且它非常复杂!)
那么为什么Lazy似乎搞乱你的默认参数情况呢?我认为这是几件事的汇合(只是一个假设):
ShowOps,调用.show一个值会导致它被隐式包装ShowOps[A].什么是A?难道会是Foo,AnyRef,Any?它会成为一种独特的单一类型吗?它并不完全清楚,因为当时没有约束A,Scala不知道你的调用.show会实际约束它(由于上下文绑定).Lazy,这可以解决问题,因为如果Scala选择了错误A并且.show没有进行类型检查,它将意识到它的错误并且退出A它所选择的.Lazy,还有一堆其他的逻辑正在进行,它有点欺骗Scala认为无论A选择什么都没关系.但是当关闭循环的时候,它就没有用了,到那时为止已经太晚了.A在ShowOps[A].