txt_flow
txt_flow

Reputation: 43

flatMap on Map with wildcard type parameter

I'am trying to write something like this:

trait Typed[T]

trait Test {

  def testMap: Map[Typed[_], Int]

  def test = testMap.flatMap {case (typed, size) => Seq.fill(size)(typed)}
}

But I get the following error:

error: no type parameters for method flatMap: (f: ((Typed[_], Int)) => Traversable[B])(implicit bf: scala.collection.generic.CanBuildFrom[scala.collection.immutable.Map[com.quarta.service.querybuilder.Typed[_],Int],B,That])That exist so that it can be applied to arguments (((Typed[_], Int)) => Seq[Typed[_0]] forSome { type _0 })
--- because ---
argument expression's type is not compatible with formal parameter type;
found   : ((Typed[_], Int)) => Seq[Typed[_0]] forSome { type _0 }
required: ((Typed[_], Int)) => Traversable[?B]
def test = testMap.flatMap {case (typed, size) => Seq.fill(size)(typed)}

This code works if change testMap type to:

def testMap: Map[Typed[Any], Int]

What is the difference and how I can solve my problem?

Upvotes: 4

Views: 1728

Answers (3)

fotNelton
fotNelton

Reputation: 3894

If I understood your question correctly, the answer is: You can do it if Typed is covariant in T, i.e. trait Typed[+T].

Example

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Typed[+T: Manifest] {
  override def toString = "Typed[" + implicitly[Manifest[T]].toString + "]"
}

trait Test {
  def testMap: Map[Typed[_], Int]

  def foo = testMap flatMap { case (t, s) => Seq.fill(s)(t) }
}

val bar = new Test { 
  def testMap = Map(new Typed[Double]() -> 3, new Typed[Int]() -> 5)
}

// Hit Ctrl-D

scala> bar.foo
res0: scala.collection.immutable.Iterable[Seq[Typed[Any]]] = List(Typed[Double], Typed[Double], Typed[Double], Typed[Int], Typed[Int], Typed[Int], Typed[Int], Typed[Int])

Note that I've made Typed a class in this example to get nicer output. You can of course stick with a trait.

Now, why is covariance needed here?

Covariance basically means that if A <: B then X[A] <: X[B]. So if you were declaring testMap as Map[Typed[Any], Int] while Typed were invariant, you were not allowed to pass in e.g. a Typed[Double] for a Typed[Any] even though Double <: Any. Here, the scala compiler seems to be replacing _ with Any in the covariant case (see extempore's comment for an elaboration on this).

For an explanation of the problem regarding the underscore, I'd refer to Luigi's answer though.

Upvotes: 5

Luigi Plinge
Luigi Plinge

Reputation: 51109

I think the problem is you're trying to pattern match an anonymous function with an existential type parameter.

From the language spec, section 8.5 regarding pattern matching anonymous functions:

The expected type of such an expression must in part be defined. It must be either scala.Functionk[S1, ... , Sk, R] for some k > 0, or scala.PartialFunction[S1, R], where the argument type(s) S1, ... , Sk must be fully determined, but the result type R may be undetermined.

testMap is an existential type (see language spec 3.2.10). An existential type has the form T forSome {Q} where Q is a sequence of type declarations. You have used the special placeholder syntax, so the type Map[Typed[_], Int] is equivalent to Map[Typed[t] forSome { type t }, Int], which may make the error message make a bit more sense.

As for a solution, I guess it depends exactly what you're trying to do, which you don't say... :)

Upvotes: 4

user unknown
user unknown

Reputation: 36250

Isn't it an option to parametrize Test?

trait Test [A] {
   def testMap: Map [Typed [A], Int]
   def test = testMap.flatMap {case (typed, size) => Seq.fill (size)(typed)}
}

Do you have to declare Test before knowing what A will be?

Upvotes: 0

Related Questions