在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被指定为参数的类型,编译器必须寻找共同的超类型Unit和Int该类型签名相匹配toSet.并且因为存在这样的类型,即AnyVal编译器将推断该类型并继续前进而不会因错误而崩溃:
scala> List(1).toSet[AnyVal](())
res9: Boolean = false
Run Code Online (Sandbox Code Playgroud)