2013-11-26 3 views
9

В Scala, почему возникает следующее при использовании функции toSet от TraversableOnce?Scala TraversableOnce and toSet

При создании рабочего листа (в IntelliJ) с помощью следующего кода вы получите следующий вывод (NB: с помощью 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

Глядя на скомпилированном байткоде, что на самом деле происходит - результирующий набор применят метод получил колл с Единичный аргумент, но не могу сказать, почему. Похоже на ошибку, поскольку она не воспроизводится, если я пытаюсь вызвать '()' on 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 (где пустой список аргументов будет рассматриваться как 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 
    ) 

Какой из них является характеристикой 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 указан как тип аргумента, компилятор должен искать общий надтип Unit и Int, который соответствует типу подписи 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, в порядке, в основном, каждая головоломка.Может быть, есть мета-головоломка, которая охватывает каждый случай края на языке. –

Смежные вопросы