开发者

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 of Iterable[P] for some P
  • It is, with P inferred as Int
  • The compiler is happy that all inference, bounds checking, etc. has succeeded
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜