Implicitly Convert Generic and Non-Generic Subtypes in Scala
Assume you want to add some methods to all Iterables. That can look like this:
import collection.generic.CanBuildFrom
class Foo[P, S[X] <: Iterable[X]](val s : S[P]) {
def bar(j : P)(implicit bf : CanBuildFrom[S[P],P,S[P]]) : S[P] = {
val builder = bf(s)
builder ++= s
builder += j
builder.result
}
def oneBar(j : P)(implicit bf : CanBuildFrom[S[P],P,S[P]]) : P = bar(j).head
开发者_Go百科}
implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)
Now, code like
println(Seq(1,2,3,4) bar 5)
compiles and executes smoothly. However,
println((1 to 4) bar 5)
causes
error: value bar is not a member of scala.collection.immutable.Range.Inclusive
with scala.collection.immutable.Range.ByOne
I figured this might be since the implicit conversion demands (?) that the parameter's type has a type parameter (which Range
has not). But
implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)
does not change anything. Note that Range
extends Iterable[Int]
.
What am I doing wrong? How can I write one implicit conversion that applies to all subtypes of Iterable
, wether generic or not?
Edit: I just notices that the much simpler
implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)
works as intended (on REPL). Or does it? Are there drawbacks of this solution?
Edit 2: The drawback is that the static result type of bar
will only be Iterable[P]
, not the more specific type. The constructed collection has the correct (actual) type, though.
Sadly, it doesn't matter that Range
extends Iterable[Int]
, this is indeed a problem with arity of type params. It's a deep one too, even the core library suffers it in places (just look at the comments in Manifest
)
You'll also encounter it if wanting to use Maps, Strings, etc. as though they were Iterable
.
The only solution I've found is to define multiple implicit conversions to the pimp type.
UPDATE
The problem here is in inferring the type parameter P
from the supplied argument, which doesn't appear to have a type parameter. You're essentially trying to do for a type-constructor what extractors will do for a regular constructor, and polymorphism is getting in the way.
Your edited example works because this particular inference isn't needed, the catch is that you can now only return an Iterable
, and so lose much of the benefit of CanBuildFrom
If that's not a problem, then it's a simpler solution, so roll with it.
Otherwise, you'll need different implicits for each possible arity of the types you want to pimp.
UPDATE 2
Consider how the compiler might handle your different expressions when trying to determine if a Range
is a valid argument:
Take 1:
implicit def iter2foo[P, S[X] <: Iterable[X]](s : S[P]) = new Foo[P,S](s)
S
is a higher-kinded type, of kind* => *
Range
is a simple type, of kind*
- The kinds don't match so it's invalid
Take 2:
implicit def iter2foo[P, S <: Iterable[P]](s : S) = new Foo[P,Iterable](s)
- Same problem,
S
is still of kind* => *
the argument doesn't match
Take 3:
implicit def iter2foo[P](s : Iterable[P]) = new Foo[P,Iterable](s)
- Having had the parameter supplied,
Iterable[P]
is a simple type of kind*
Range
passes this first hurdle- The second check is that
Range
is a subclass ofIterable[P]
for someP
- It is, with
P
inferred asInt
- The compiler is happy that all inference, bounds checking, etc. has succeeded
精彩评论