I have been relearning Scala after several years of being away from it. I am now trying to make sense of implicits and implicit resolution (which was always a problem in the past for me). I ran into something that indicates that I may have a more fundamental misunderstanding than I thought - especially with scoping and imports.
Consider the following:
class A {
def test(using n: Int): String = n.toString
}
object A {
given n: Int = 2
}
object Main extends App {
println((new A()).test)
}
If I try to compile this I get the following error:
no implicit argument of type Int was found for parameter n of method test in class A
I don't understand why this is the case. If I change the print expression to the following:
println((new A()).test(using A.n))
the it works (and it took me a long time to find out that this was, in fact, an allowable syntax).
The other solution, apparently, is to do the following:
import A.given
println((new A()).test)
and this works as well.
From this answer implicits are searched in the companion object of a type (which is the case here). So, my question is, I suppose, why is it that the compiler allows me to directly reference A.n
but it cannot find that implicit value on its own? Why is an import needed? It looks very much like it is in scope and should be resolved.
Part of my confusion comes from the documentation:
import
clauses are for accessing members (classes, traits, functions, etc.) in other packages. An import clause is not required for accessing members of the same package.
(from the docs). As object A
is "part of the same package" why do I need to import it or any of its members? And, in general, when is it necessary to import members when the class or object is in scope and visible? Is this something that has changed in Scala3?
Edit
Just playing around I also tried the following:
class A {
def test(using n: Int): String = n.toString
}
object A {
given n: Int = 2
}
given n: Int = 5
object Main extends App {
import A.given
println((new A()).test)
}
I expected to get a compiler error about an ambiguous implicit resolution, but it worked and printed 2
. I don't understand why this worked. If I comment out the import it prints 5
which is, at least, consistent with what I saw above.
CodePudding user response:
no implicit argument of type Int was found for parameter n of method test in class A
Which is expected, there is no implicit
/ given
Int
in scope in that case.
PS: I guess you are already aware and this is just an exercise, but something like Int
should never be implicit
I don't understand why this is the case. If I change the print expression to the following:
println((new A()).test(using A.n))
then it works
Well, in that case you are explicitly passing the parameter, there is no implicit resolution going on anymore.
You could have even done something like
println((new A()).test(using 3))
Is the same, there is nothing fancy happening there.
The other solution, apparently, is to do the following:
import A.given
In that case, you are adding all the given
values inside the object A
to the current scope.
This, of course, fixes the original problem because now there is a given Int
in scope and implicit resolution works as expected.
From this answer implicits are searched in the companion object of a type (which is the case here).
It is not actually.
The type here is Int
and the companion object
of Int
does not define any given
value.
So, my question is, I suppose, why is it that the compiler allows me to directly reference A.n but it cannot find that implicit value on its own?
It allows you to reference it because the compiler will always allow you to reference any value using a relative or full path (as long as the visibility constraints are meant). Which is just the same you do when you write any code, there is nothing related to implicits here.
And, again, the compiler can not find it because such value is not in the implicit scope so the compiler won't look for it.
(from the docs). As object A is "part of the same package" why do I need to import it or any of its members?
You don't need to import it to access its members, just as you showed before, you can easily access A.n
I expected to get a compiler error about an ambiguous implicit resolution, but it worked and printed 2. I don't understand why this worked. If I comment out the import it prints 5 which is, at least, consistent with what I saw above.
This one is actually tricky, but implicit resolution has a concept of weight. The specs say that if two implicit values are found in scope the most specific one will be picked. Only if both have the same weight, then it is when it will raise an ambiguous error.
In the case of imports, they always have more weight. And further imports "shadow" previous ones (this also happens with regular values btw).
And advice, maybe it would be easier to understand if you use a real example; not something like Int