Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

toSet and type inference

Tags:

scala

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))
like image 812
regexp Avatar asked Oct 07 '14 14:10

regexp


1 Answers

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.

like image 161
Travis Brown Avatar answered Oct 19 '22 17:10

Travis Brown