11

Can somebody explain why the following does not work. Somehow looses the compile some information for the type inference when i do toSet, but i don't understand why.

scala> case class Foo(id: Int, name: String)
defined class Foo

scala> val ids = List(1,2,3)
ids: List[Int] = List(1, 2, 3)

scala> ids.toSet.map(Foo(_, "bar"))
<console>:11: error: missing parameter type for expanded function ((x$1) => Foo(x$1, "bar"))
              ids.toSet.map(Foo(_, "bar"))
                                ^

scala> ids.map(Foo(_, "bar")).toSet
res1: scala.collection.immutable.Set[Foo] = Set(Foo(1,bar), Foo(2,bar), Foo(3,bar))
regexp
  • 766
  • 4
  • 14
  • 1
    Seems the compiler needs some help making the type explicit, `ids.toSet.map(Foo(_: Int, "bar"))` – jarandaf Oct 07 '14 at 14:12
  • Yes, but why he doesn't the compiler need the information when i do the toSet after the map?? – regexp Oct 07 '14 at 14:31
  • 1
    Confusingly, this works, `val a = ids.toSet ; a.map(Foo(_, "bar"))` – elm Oct 07 '14 at 14:35
  • Consider a similar error here: http://stackoverflow.com/q/4701761/3189923 – elm Oct 07 '14 at 14:43
  • 1
    @enzyme: despite a superficial similarity, this is not really a similar error. In OP's case, the error has all to do with the use of `toSet`. Note that when replacing `toSet` with `toSeq` it compiles fine. – Régis Jean-Gilles Oct 07 '14 at 14:53
  • @RégisJean-Gilles totally agree. – elm Oct 07 '14 at 18:44
  • possible duplicate of [Type inference on Set failing?](http://stackoverflow.com/questions/5544536/type-inference-on-set-failing) – Seth Tisue Sep 25 '15 at 14:35

1 Answers1

7

Suppose I've got the following:

trait Pet {
  def name: String
}

case class Dog(name: String) extends Pet

val someDogs: List[Dog] = List(Dog("Fido"), Dog("Rover"), Dog("Sam"))

Set isn't covariant in its type parameter, but List is. This means if I have a List[Dog] I also have a List[Pet], but a Set[Dog] is not a Set[Pet]. For the sake of convenience, Scala allows you to upcast during a conversion from a List (or other collection types) to a Set by providing an explicit type parameter on toSet. When you write val a = ids.toSet; a.map(...), this type parameter is inferred and you're fine. When you write ids.toSet.map(...), on the other hand, it's not inferred, and you're out of luck.

This allows the following to work:

scala> val twoPetSet: Set[Pet] = someDogs.toSet.take(2)
twoPetSet: Set[Pet] = Set(Dog(Fido), Dog(Rover))

While this doesn't:

scala> val allDogSet: Set[Dog] = someDogs.toSet
allDogSet: Set[Dog] = Set(Dog(Fido), Dog(Rover), Dog(Sam))

scala> val twoPetSet: Set[Pet] = allDogSet.take(2)
<console>:14: error: type mismatch;
 found   : scala.collection.immutable.Set[Dog]
 required: Set[Pet]
Note: Dog <: Pet, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Pet`. (SLS 3.2.10)
       val twoPetSet: Set[Pet] = allDogSet.take(2)
                                               ^

Is this worth the confusion? I don't know. But it kind of makes sense, and it's the decision the Collections API designers made for toSet, so we're stuck with it.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • One confusing thing about this is that the inability to infer a type for `toSet` gets expressed by the compiler as an inability to infer a type for `Foo(_, "bar")`. The inferencing seems to work in either direction: `ids.toSet[Int].map(Foo(_, "bar"))` doesn't complain, and neither does `ids.toSet.map(Foo(_: Int, "bar"))`. – Joe Pallas Oct 07 '14 at 17:06
  • @Joe: The issue is that `map` needs to know the type of the argument of its argument. If the type of the set it's being called on isn't known or hasn't been inferred yet, you have to provide that type explicitly. – Travis Brown Oct 07 '14 at 17:26