Scala TraversableOnce和toSet

Bee*_*Bee 9 scala

Scala中,为什么在使用以下toSet功能时会发生以下情况TraversableOnce

如果使用以下代码创建工作表(在IntelliJ中),您将获得以下输出(注意:使用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()
Run Code Online (Sandbox Code Playgroud)

即res4产生一个布尔值

> 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
Run Code Online (Sandbox Code Playgroud)

毋庸置疑,我很困惑,直到我注意到toSet在实现中没有使用括号,但为什么布尔值?

kir*_*uku 14

正如您和其他人已经注意到的那样,toSet不提供参数列表.因此,使用括号调用它将始终导致编译错误,除非编译器找到需要参数的apply方法,因为在您的示例中就是这种情况:

scala> List(1).toSet()
res2: Boolean = false

scala> List(1).toSet.apply()
res3: Boolean = false
Run Code Online (Sandbox Code Playgroud)

scalac有一个名为"自适应参数列表"的功能,可以看到-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
Run Code Online (Sandbox Code Playgroud)

scalac尝试将参数包装到元组中,就像在源中可以看到的那样(其中空参数列表将被视为Unit文字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
      )
Run Code Online (Sandbox Code Playgroud)

哪个btw,是规范中没有提到的功能.明确添加括号将使警告消失:

scala> List(1).toSet(())
res8: Boolean = false
Run Code Online (Sandbox Code Playgroud)

现在剩下的一个问题是为什么上面的代码不会产生编译错误,因为列表是类型List[Int],而apply方法是Set类型签名apply(A): Boolean,因此Int在我们的例子中需要一个.其原因是一个众所周知的问题,其类型签名的toSet结果是toSet[B >: A]: Set[B].类型签名表示下限,这意味着任何超类型Int都可以作为参数传递.

因为在我们的情况下,Unit被指定为参数的类型,编译器必须寻找共同的超类型UnitInt该类型签名相匹配toSet.并且因为存在这样的类型,即AnyVal编译器将推断该类型并继续前进而不会因错误而崩溃:

scala> List(1).toSet[AnyVal](())
res9: Boolean = false
Run Code Online (Sandbox Code Playgroud)