【问题标题】:Scala TraversableOnce and toSetScala TraversableOnce 和 toSet
【发布时间】:2013-11-26 18:09:30
【问题描述】:

Scala 中,为什么在使用 TraversableOnce 中的 toSet 功能时会出现以下情况?

如果您使用以下代码创建工作表(在 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()

即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

不用说我很困惑很长时间,直到我注意到 toSet 在实现中不使用括号,但为什么是布尔值?

【问题讨论】:

  • 查看编译后的字节码,实际发生了什么——结果集的 apply 方法被使用 Unit 参数调用,但无法说明原因。看起来像一个错误,因为如果我尝试在 res3 上调用 (),它是不可重现的

标签: scala


【解决方案1】:

正如您和其他人已经注意到的那样,toSet 不提供参数列表。因此,用括号调用它总是会导致编译错误,除非编译器找到一个需要参数的 apply 方法,就像您的示例中的情况一样:

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

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

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

scalac 尝试将参数包装到一个元组中,正如在sources 中所见(其中一个空参数列表将被gen.mkTuple 视为Unit 文字):

      /* 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
      )

顺便说一句,not mentioned in the spec 是一项功能。显式添加括号将使警告消失:

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

现在剩下的一个问题是为什么上面的代码没有产生编译错误,因为列表的类型是List[Int],而Set的apply方法有类型签名apply(A): Boolean,因此需要一个@ 987654341@ 在我们的例子中。其原因是very well known problemtoSet 的类型签名的结果,即toSet[B &gt;: A]: Set[B]。类型签名表示一个下限,这意味着Int 的任何超类型都可以作为参数传递。

因为在我们的例子中Unit 被指定为参数的类型,编译器必须搜索与toSet 的类型签名匹配的UnitInt 的公共超类型。而且因为有这样一种类型,即AnyVal,编译器会推断出该类型并继续前进而不会因错误而崩溃:

scala> List(1).toSet[AnyVal](())
res9: Boolean = false

【讨论】:

猜你喜欢
  • 2016-04-05
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 2020-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多