我会尝试发布我自己的答案,稍后会改进它。让我们从一个激励场景开始,但您可以跳到下面的 TLDR,然后根据需要返回这里。
在一种情况下,证据参数可以被视为一种通过原始定义之外的某些行为(方法/s)来丰富类的方法。
对Cake Solutions 的精彩帖子进行了温和的重新讨论:
如果您之前没有直观地编写过这样的代码,这里有一个正在使用的证据参数的代码演示。
object EvidenceExample {
// class with no methods
case class Bar(value: String)
// a trait the class Bar had not implemented
trait WithFoo[A] {
def foo(x: A): String
}
// object that attaches an implementation of the trait for Bar - for methods
// willing to play along with this kind of trait attachment - see immediately below
implicit object MakeItFoo extends WithFoo[Bar] {
def foo(x: Bar) = x.value
}
// method willing to recognize anything as having trait WithFoo,
// as long as it has evidence that it does - the evidence being the previous object
def callFoo[A](thing: A)(implicit evidence: WithFoo[A]) = evidence.foo(thing)
callFoo(Bar("hi")) // and it works
}
您可能会从下往上阅读该代码,发现类 Bar 已在其原始定义之外进行了丰富。然而——只有与证据仪式一起发挥作用的功能才能将其视为丰富。
这种模式几乎没有什么神奇之处——尽管这是一种独特的语言特性——包装器对象将特征与Bar 相关联,callFoo 依赖于该关联。
我们甚至可以在没有隐式的情况下编写相同的模式,但是最后一行,即调用该方法的那一行,将需要一个额外的参数——是否使用隐式的经济学——完全取决于你。
您可以根据需要对其进行加糖或减糖,例如,这里有一个小的语法改进:
(这里只修改了最后一个def,现在移除了cmets)
object EquivalentEvidenceExample {
case class Bar(value: String)
// a trait the class Bar had not implemented
trait WithFoo[A] {
def foo(x: A): String
}
implicit object MakeItFoo extends WithFoo[Bar] {
def foo(x: Bar) = x.value
}
def callFoo[A:WithFoo](thing: A) = implicitly[WithFoo[A]].foo(thing) // lightly sugared syntax, frankly only more confusing
callFoo(Bar("hi"))
}
您无需使用字符串evidence 命名任何内容。编译器只是通过在所有这些等效情况下使用它的方式知道这是一个证据参数。
更一般地或词源上,borrowing from the other answer,证据参数是“证明”一种类型的特定属性的参数,编译器需要它,只要方法的签名表明这样的要求(在另一个答案中,有没有为类型Any 提供<:< Foo 的证据,这是方法签名所要求的,因此这是缺少证据的情况。
Failure 将证据对象作为隐式提供,将导致著名的could not find implicit value for evidence parameter of type ...,因为编译器知道这是证据模式的一部分,而不仅仅是缺少的隐式(同样因为这种差异对您很重要)。
TLDR:
简而言之,某个类S 的证据参数是T[S] 类型的参数(因此,是一个类的参数),它定义了关于S 的一个或多个事物——因此“证明”某些事物关于S —— 这使得S 有资格被调用者扩展使用,超出S 的原始定义。 T[S] 应该具有的确切形状在我上面借用的示例中得到了例证,implicit object MakeItFoo。