I've been trying to find an authoritative definition of what is an evidence parameter, to no avail, for solving a case of "could not find implicit value for evidence parameter of type....". Can you please provide a very good explanation for what an evidence parameter exactly is?
问题:
回答1:
I'll try posting my own answer, improving it later as it goes. Let us start with a motivational scenario, but you can jump to the TLDR below then come back here as needed.
In one scenario, evidence parameters can be seen as a means of enriching a class with some behavior (method/s) from outside its original definition.
A mild rehash of the great post at Cake Solutions:
In case you have not already intuitively written such code before, here's a code demonstration of evidence parameters in use.
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
}
You might read that code from the bottom up to realize that a class Bar
has been enriched outside its original definition. Yet ― only functions that play along with the evidence ceremony can see it as enriched.
There's very little magic going on in this pattern ― although this is a unique language feature ― the wrapper object associates the trait to Bar
, and callFoo
relies on that association.
We could even write the same pattern without implicits, but then the last line, the one that calls the method, would need an extra parameter ― the economics of whether to use an implicit or not ― are entirely up to you.
You can up-sugar or down-sugar it as you wish, for example here's a minor syntax improvement:
(only the last def
herein modified, and the comments removed now)
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"))
}
And there's nothing requiring of you to name anything with the string evidence
. The compiler just knows this is an evidence parameter by the way it is used in all these equivalent cases.
More generally or etymologically, borrowing from the other answer, an evidence parameter is one that "evidences" a specific property of a type, and it is required by the compiler wherever a method's signature manifests such a requirement (in the other answer, there is no evidence supplied for type Any
being <:< Foo
, as required by the method's signature, hence it is a case of a missing evidence).
Failure to the have an evidence object available as an implicit, will result in the famous could not find implicit value for evidence parameter of type ...
because the compiler knows this is part of an evidence pattern and not just a missing implicit (as much as this difference matters to you).
TLDR:
Succinctly speaking, an evidence parameter for some class S
is a parameter of a type T[S]
(so, a parameter that is a class) that defines one or more things about S
― thus "evidencing" something about S
― that makes S
eligible for extended usage by a caller, beyond the original definition of S
. The exact shape such a T[S]
should have, is exemplified in my borrowed examples above, by implicit object MakeItFoo
.
回答2:
The language specification uses the term "evidence" in §7.4 Context Bounds and View Bounds:
A type parameter
A
of a method or non-trait class may also have one or more context boundsA : T
. In this case the type parameter may be instantiated to any typeS
for which evidence exists at the instantiation point thatS
satisfies the boundT
. Such evidence consists of an implicit value with typeT[S]
.
With this syntactic sugar you get synthetic parameters which the spec calls "evidence parameters". (Note that this also covers view-bounds <%
which are now deprecated).
Since often explicitly written implicit parameters are also named evidence
, I think it's valid to call any implicit parameter an "evidence" if it witnesses a specific property of a type.
Take for example <:< [A, B]
that evidences that A
is a sub-type of B
:
trait Foo
trait Bar[A] {
def baz(implicit evidence: A <:< Foo): Unit
}
Then if you try this:
trait Test {
def bar: Bar[Any]
bar.baz
}
This fails with a compilation error:
<console>:58: error: Cannot prove that Any <:< Foo.
bar.baz
^
The exact wording can be specified with the implicitNotFound
annotation. Without a specific code example, it's unclear what generates the "could not find implicit value for evidence parameter of type....".
Here's an example of a custom message:
@annotation.implicitNotFound(msg = "Oh noes! No type class for ${A}")
trait MyTypeClass[A]
trait Bar[A] {
def baz(implicit evidence: MyTypeClass[A]): Unit
}
Then:
trait Test {
def bar: Bar[Any]
bar.baz
}
Fails with the custom message:
<console>:58: error: Oh noes! No type class for Any
bar.baz
^