2013-11-26 54 views
9

In Scala,爲什麼在使用TraversableOncetoSet功能時發生以下情況?Scala TraversableOnce和Set

如果您創建一個工作表(在的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在實現中沒有使用圓括號,但爲什麼布爾值?

+1

望着編譯的字節代碼,實際上發生了什麼 - 造成集的應用方法得到了調用單位論證,但不能說爲什麼。看起來像一個錯誤,因爲如果我嘗試在res3 –

回答

14

由於您和其他人已經注意到,toSet未提供參數列表。因此,用括號調用它,總是會導致一個編譯錯誤,除非編譯器發現一個應用一個期望的參數的方法,因爲它是在你的榜樣的情況:

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 
    ) 

哪btw,是一個功能是not mentioned in the spec。添加括號明確會讓警告消失:現在

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

剩下的一個問題是,爲什麼上面的代碼不會產生編譯錯誤由於該列表是List[Int]類型和適用的Set方法有類型簽名apply(A): Boolean,因此在我們的情況下期望Int。這是因爲very well known problem和類型簽名toSet的結果是toSet[B >: A]: Set[B]。類型簽名表示下限,這意味着任何超類型Int都可以作爲參數傳遞。

因爲在我們的情況下Unit被指定爲參數的類型,編譯器必須搜索的UnitInt一個共同的超認爲的toSet類型簽名匹配。而且因爲有這樣的類型,即AnyVal,編譯器會推斷類型和前進而不會出現錯誤分崩離析:

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

Bravo上調用'()',它就無法重現! (角)獸被殺了! –

+1

_really_應該在規範中 - 考慮[投票](https://issues.scala-lang.org/browse/SI-3583)。 –

+1

很好的解釋,但沒有答案是完整的,沒有參考益智遊戲。 http://scalapuzzlers.com/#pzzlr-040和相關的http://scalapuzzlers.com/#pzzlr-036和http://scalapuzzlers.com/#pzzlr-032,好的,基本上每一個益智遊戲。也許有一種涵蓋語言中每個邊緣案例的meta-puzzler。 –

相關問題