The question is why doesn't the following code work with type inference (below is a REPL session to demonstrate), and can it be fixed? More specifically, how is this different from the use of CanBuildFrom which the compiler uses to infer the return type?
Given this code:
object S {
import java.net._
trait UrlLike[T] {
def url(s: String): T
}
object UrlLike {
implicit object str extends UrlLike[String]{def url(s: String) = s}
implicit object url extends UrlLike[URL]{def url(s: String) = new URL(s)}
implicit object uri extends UrlLike[URI]{def url(s: String) = new URI(s)}
}
trait UrlSupport {
val _url: String
def url[T : UrlLike]: T = implicitly[UrlLike[T]].url(_url)
}
}
I have this session in the REPL (2.8.1):
scala> :load c:\temp\UrlTest.scala
Loading c:\temp\UrlTest.scala...
defined module S
scala> import java.net._
import java.net._
scala> import S._
import S._
scala> new UrlSupport{val _url = "http://example.com"}
res0: java.lang.Object with S.UrlSupport = $anon$1@155bd22
scala> res0.url : String
<console>:14: error: ambiguous implicit values:
both object uri in object UrlLike of type object S.UrlLike.uri
and object url in object UrlLike of type object S.UrlLike.url
match expected type S.UrlLike[T]
res0.url : String
^
scala> res0.url : URL
<console>:14: error: ambiguous implicit values:
both object uri in object UrlLike of type object S.UrlLike.uri
and object url in object UrlLike of type object S.UrlLike.url
match expected type S.UrlLike[T]
res0.url : URL
^
scala> res0.url[String]
res3: String = http://example.com
scala> res0.url[URL]
res4: java.net.URL = http://example.com
I can see why you'd expect it to work, but, obviously, the type inferencer isn't using the return type to infer T
. I'd expect it too as well.
As for the ambiguity, CanBuildFrom
avoids being ambiguous by just not defining everything in the same "level". For instance, this solves the ambiguity problem:
trait LowPriorityImplicits {
implicit object url extends UrlLike[URL]{def url(s: String) = new URL(s)}
implicit object uri extends UrlLike[URI]{def url(s: String) = new URI(s)}
}
object UrlLike extends LowPriorityImplicits {
implicit object str extends UrlLike[String]{def url(s: String) = s}
}
However, it will not make the type inference work the way you want:
scala> res0.url : URL
<console>:16: error: type mismatch;
found : String
required: java.net.URL
res0.url : URL
^
Which indicates it obviously makes T
inference without taking into consideration the return type.
> trait UrlLike[T] {
trait UrlLike[+T] {
Regarding any implicit ambiguity, the rule is (since Scala2.8):
When comparing two different applicable alternatives of an overloaded method or of an implicit, each method:
- gets one point for having more specific arguments,
- and another point for being defined in a proper subclass.
An alternative “wins” over another if it gets a greater number of points in these two comparisons.
This means in particular that if alternatives have identical argument types, the one which is defined in a subclass wins.
I don't think the implicits around URL
or URI
get a different set of points following those criteria.
It might not be obvious from the error report that you're seeing, but all three of the implicits defined in object UrlLike are contributing to the ambiguity (eg. try commenting out the definition of uri and you'll see the ambiguity reported as being between str and url).
The reason for the ambiguity is that the type parameter T of UrlSupport.url is bounded only by the requirement that there be an implicit UrlLike instance available for it. String, URL and URI all satisfy that requirement equally well thanks the UrlLike instances provided by your three implicit objects. The compiler isn't going to choose one of those for you arbitrarily, so it reports an ambiguity.
Unless, of course, you resolve the ambiguity by explicitly specifying the type argument, as you've done in the last two of your REPL interactions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With