Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala TraversableOnce and toSet

Tags:

scala

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?

like image 525
Bee Avatar asked Nov 26 '13 18:11

Bee


People also ask

What is the use of method traversableonce in Scala?

(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:

What is the traversable collection in Java?

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.

What is the use of iterator traversable trait?

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.

Can I subclass traversableonce?

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.


1 Answers

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
like image 80
kiritsuku Avatar answered Oct 23 '22 14:10

kiritsuku