Scala 不成形的';惰性参数和默认参数会导致隐式解析失败
我的一个项目混合使用了一些scala功能,这些功能似乎不能很好地结合在一起:Scala 不成形的';惰性参数和默认参数会导致隐式解析失败,scala,shapeless,Scala,Shapeless,我的一个项目混合使用了一些scala功能,这些功能似乎不能很好地结合在一起: 类型类和无形状的自动类型类实例派生 隐式转换(向具有类型类实例的类型添加有用的语法) 默认参数,因为尽管它们通常是一件坏事,但它们在这里太方便了 我遇到的问题是类型类实例派生失败,如果: 默认参数没有明确指定 无形状派生使用Lazy 以下是我为重现问题所能编写的尽可能少的代码量: 斯卡拉 编译失败,错误消息如下: Run.scala:10: could not find implicit value for p
- 类型类和无形状的自动类型类实例派生
- 隐式转换(向具有类型类实例的类型添加有用的语法)
- 默认参数,因为尽管它们通常是一件坏事,但它们在这里太方便了
- 默认参数没有明确指定
- 无形状派生使用
Lazy
Run.scala:10: could not find implicit value for parameter sa: Show[Run.Foo]
[error] println(Foo(12).show())
编译错误通过以下任一方法修复:
- 将
参数显式传递给标题
Run.scala中的
show
- 在
Show.scala
我必须承认我在这里完全不知所措。我很想了解会发生什么,如果有解决办法,我也很想知道。简短回答: 如果将上下文绑定到隐式类,它也可以正常工作。要做到这一点,您必须牺牲value类,但我认为事先告诉编译器只有具有
Show
的A
s才能通过它得到丰富也是更为简洁的:
implicit class Show2Ops[A : Show](a: A) {
def show2(header: String = "> ") = header + implicitly[Show[A]].show(a)
}
println(Foo(12).show2())
长理论:
Lazy
做了一些有趣的把戏,很难遵循。你没有特别问过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
}
implicit val-hnil:Show[hnil]=Show.from(=>“”)
隐式def-hcons[H,T-showH(H)+,“+showT(T)//例如
}
让我们将singletonCaseClassShow
重命名为genericShow
,因为它不再只适用于单身人士
现在让我们假设您在genericShow
中没有Lazy
。当您尝试调用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
克服这一问题的方法是在编译器试图具体化它的隐式实例时进入宏。因此,当您在hcons
中使用implicit Show:Lazy[Show[H]]
而不仅仅是Show[H]
时,编译器会转到该宏来查找Lazy[Show[H]]
而不是停留在隐式的展示
案例中
宏检查打开的隐式(宏有权访问该隐式),并进入其自己的隐式解析算法,该算法在继续查找T
(对于Lazy[T]
)的隐式实例之前始终完全解析打开的隐式。如果要解析一个已经打开的隐式,它将替换一个虚拟树(本质上是告诉编译器“我得到了这个,不要担心它”),该树跟踪打结的依赖项,以便解析的其余部分可以完成。最后,它将清理虚拟树(我不太明白它是如何工作的;那里的代码数量惊人,而且相当复杂!)
那么为什么Lazy
似乎会把默认的参数设置搞砸呢?我认为这是几个因素的综合(只是一个假设):
使用原始的ShowOps
,对值调用.show
会导致将其隐式包装在ShowOps[a]中
。A将是什么?它将是Foo
,AnyRef
,Any
?它将是一个唯一的单一类型吗?它并不完全清楚,因为那时对A
没有约束,Scala也不知道对的调用实际上会约束它(由于上下文的限制)
如果没有Lazy
,这就行了,因为如果Scala选择了错误的A
,并且.show
没有进行打字检查,它将意识到自己的错误并退出它选择的A
由于Lazy
,还有一系列其他的逻辑在进行,这有点欺骗Scala,使其认为无论它选择了什么a
,都是好的。但是,当到了结束循环的时候,它就不起作用了,到那时,退出已经太晚了
不知何故,未指定的默认参数会影响Scala在ShowOps[A]
中选择A
的初始选择
我知道我不应该只是评论说
implicit def opt[A](implicit showA: Show[A]): Show[Option[A]] = Show.from {
case Some(a) => s"Some(${showA.show(a)})"
case None => "None"
}
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
}