The book says to place the “second choice” given in a “lower priority trait” and place the “first choice” given in a subclass or sub-object of that trait. So you have to move one of the givens elsewhere.
From Section 21.7:
21.7 When multiple givens apply
It can happen that multiple givens are in scope and each would work. For
the most part, Scala refuses to fill in a context parameter in such a case.
Context parameters work well when the parameter list left out is completely
obvious and pure boilerplate. If multiple givens apply, then the choice isn’t
so obvious after all. As an example, take a look at Listing 21.7.
class PreferredPrompt(val preference: String)
object Greeter:
def greet(name: String)(using prompt: PreferredPrompt) =
println(s"Welcome, $name. The system is ready.")
println(prompt.preference)
object JillsPrefs:
given jillsPrompt: PreferredPrompt = PreferredPrompt("Your wish> ")
object JoesPrefs:
given joesPrompt: PreferredPrompt = PreferredPrompt("relax> ")
Listing 21.7 · Multiple givens.
Both JillsPrefs
and the JoesPrefs
objects shown in Listing 21.7 of-
fer a given PreferredPrompt
. If you import both of these, there will be two
different identifiers in lexical scope, jillsPrompt
and joesPrompt
:
scala> import JillsPrefs.jillsPrompt
scala> import JoesPrefs.joesPrompt
If you try to invoke Greeter.greet now, the compiler will refuse to choose
between the two applicable givens.
scala> Greeter.greet("Who's there?")
1 |Greeter.greet("Who's there?")
|
ˆ
|ambiguous implicit arguments: both given instance
|joesPrompt in object JoesPrefs and given instance
|jillsPrompt in object JillsPrefs match type
|PreferredPrompt of parameter prompt of method
|greet in object Greeter
The ambiguity here is real. Jill’s preferred prompt is completely differ-
ent from Joe’s. In this case, the programmer should specify which one is
intended and be explicit. Whenever multiple givens could be applied, the
compiler will refuse to choose between them—unless one is more specific
than the other. The situation is just as with method overloading. If you try to
call foo(null)
and there are two different foo overloads that accept null,
the compiler will refuse. It will say that the method call’s target is ambigu-
ous.
If one of the available givens is strictly more specific than the others,
however, then the compiler will choose the more specific one. The idea
is that whenever there is a reason to believe a programmer would always
choose one of the givens over the others, don’t require the programmer to
write it explicitly. After all, method overloading has the same relaxation.
Continuing the previous example, if one of the available foo methods takes
a String
while the other takes an Any
, then choose the String
version. It’s clearly more specific.
To be more precise, one given is more specific than another if one of the
following applies:
• The type of the former is a subtype of the latter’s.
• The enclosing class of the former extends the enclosing class of the
latter.
If you have two givens that could be ambiguous, but for which there is
an obvious first and second choice, you can place the second choice in a
“LowPriority
” trait and the first choice in a subclass or sub-object of that
trait. The first choice will be taken by the compiler if it is applicable, even if
the lower priority choice would otherwise be ambiguous. If the higher prior-
ity given is not applicable, but the lower priority given is, the compiler will
use the lower priority given.