In Scala, why does the following occur when using the toSet
functionality from TraversableOnce
?
If you create a worksheet (in IntelliJ) with the following code you will get the following output (NB: using Scala 2.10.2):
val maps = List(List(1,2),List(3,4),List(5,6,7),List(8),List())
maps.flatMap( _.map( _ + " " ) )
maps.flatMap( _.map( _ + " " ) ).toSet
maps.flatMap( _.map( _ + " " ) ).toSet()
i.e. res4 produces a boolean
> maps: List[List[Int]] = List(List(1, 2), List(3, 4), List(5, 6, 7), List(8), List())
> res2: List[String] = List("1 ", "2 ", "3 ", "4 ", "5 ", "6 ", "7 ", "8 ")
> res3: scala.collection.immutable.Set[String] = Set("3 ", "8 ", "4 ", "5 ", "1 ", "6 ", "2 ", "7 ")
> res4: Boolean = false
Needless to say I was a confused for long enough until I noted toSet
doesn't use parentheses in the implementation, but why the boolean?
(defined at scala.collection.TraversableOnce) Appends all elements of this traversable or iterator to a string builder using a separator string. The written text consists of the string representations (w.r.t. the method toString ) of all elements of this traversable or iterator, separated by the string sep . Example:
At the top of the collection hierarchy is trait Traversable. Its only abstract operation is foreach: Collection classes that implement Traversable just need to define this method; all other methods can be inherited from Traversable.
This trait exists primarily to eliminate code duplication between Iterator and Traversable , and thus implements some of the common methods that can be implemented solely in terms of foreach without access to a Builder . It also includes a number of abstract methods whose implementations are provided by Iterator , Traversable , etc.
Directly subclassing TraversableOnce is not recommended - instead, consider declaring an Iterator with a next and hasNext method, creating an Iterator with one of the methods on the Iterator object, or declaring a subclass of Traversable . Tests whether a predicate holds for at least one element of this traversable or iterator.
As you and others have already noticed, toSet
doesn't provide a parameter list. Thus, calling it with parentheses, will always result in a compilation error unless the compiler finds an apply method that expects an argument as it is the case in your example:
scala> List(1).toSet()
res2: Boolean = false
scala> List(1).toSet.apply()
res3: Boolean = false
scalac has a feature called "adapting argument lists", which is visible with -Xlint
:
scala> List(1).toSet()
<console>:8: warning: Adapting argument list by inserting (): this is unlikely to be what you want.
signature: GenSetLike.apply(elem: A): Boolean
given arguments: <none>
after adaptation: GenSetLike((): Unit)
List(1).toSet()
^
res7: Boolean = false
scalac tries to wrap the argument into a tuple, as one can see in the sources (where an empty argument list will be treated as the Unit
literal by gen.mkTuple
):
/* Try packing all arguments into a Tuple and apply `fun`
* to that. This is the last thing which is tried (after
* default arguments)
*/
def tryTupleApply: Tree = (
if (eligibleForTupleConversion(paramTypes, argslen) && !phase.erasedTypes) {
val tupleArgs = List(atPos(tree.pos.makeTransparent)(gen.mkTuple(args)))
// expected one argument, but got 0 or >1 ==> try applying to tuple
// the inner "doTypedApply" does "extractUndetparams" => restore when it fails
val savedUndetparams = context.undetparams
silent(_.doTypedApply(tree, fun, tupleArgs, mode, pt)) map { t =>
// Depending on user options, may warn or error here if
// a Unit or tuple was inserted.
val keepTree = (
!mode.typingExprNotFun
|| t.symbol == null
|| checkValidAdaptation(t, args)
)
if (keepTree) t else EmptyTree
} orElse { _ => context.undetparams = savedUndetparams ; EmptyTree }
}
else EmptyTree
)
Which btw, is a feature that is not mentioned in the spec. Adding the parentheses explicitly will let the warning go away:
scala> List(1).toSet(())
res8: Boolean = false
One remaining question now is why the code above does not produce a compilation error due to the fact that the list is of type List[Int]
and the apply method of Set
has type signature apply(A): Boolean
, thus expects an Int
in our case. The reason for this is a very well known problem and a result of the type signature of toSet
which is toSet[B >: A]: Set[B]
. The type signature denotes a lower bound, which means that any supertype of Int
can be passed as argument.
Because in our case Unit
is specified as the type of the argument, the compiler has to search for a common supertype of Unit
and Int
that matches the type signature of toSet
. And because there is such a type, namely AnyVal
, the compiler will infer that type and going forward without falling apart with an error:
scala> List(1).toSet[AnyVal](())
res9: Boolean = false
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With