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
}
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
RangeextendsIterable[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 inManifest)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
Pfrom 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 ofCanBuildFromIf 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
Rangeis a valid argument:Take 1:
Sis a higher-kinded type, of kind* => *Rangeis a simple type, of kind*Take 2:
Sis still of kind* => *the argument doesn’t matchTake 3:
Iterable[P]is a simple type of kind*Rangepasses this first hurdleRangeis a subclass ofIterable[P]for somePPinferred asInt