1, "b" -> 1)val test1: Iterable[String] = foo.keysval test2: Iterator[String] = foo.keys.toIteratorprintln(test1.map(foo).size) /" />

Почему этот Iterable создает Set после отображения?

scala dictionary collections iterator iterable

399 просмотра

3 ответа

В приведенном ниже примере кода почему Iterable [String] test1создает Set после сопоставления?

val foo = Map("a" -> 1, "b" -> 1)
val test1: Iterable[String] = foo.keys
val test2: Iterator[String] = foo.keys.toIterator

println(test1.map(foo).size) // 1
println(test2.map(foo).size) // 2

Я был озадачен этим, потому что это совершенно нелогично при чтении кода. Хотя foo.keysпросто возвращает Iterable, он создает Set при вызове map, как показывает код отражения:

println(test1.map(foo).getClass.getName) // immutable.Set.Set1
println(test2.map(foo).getClass.getName) // Iterator$$anon$11

Как стандартная библиотека определяет, что она должна создавать immutable.Setздесь, даже если предполагаемый тип коллекции является справедливым Iterable[String]?

Автор: Chris Источник Размещён: 08.11.2019 11:32

Ответы (3)


2 плюса

Решение

Раскопав комментарий Колмара о том, что, хотя в игре присутствует неявный аргумент, который определяет, как создается коллекция результатов, в этом случае исходная коллекция просто запрашивается для использования сборщиком.

Iterable.map:

def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[Iterable[A], B, That]): That

Неявная область действия включает типы, связанные с аргументами типов, включая Iterableи Int.

Iterableопределяет «универсальный», CanBuildFromкоторый вызывает genericBuilderисходную коллекцию. Вот как тип результата привязан к источнику.

И наоборот, коллекция результатов отделена от источника, взяв a CanBuildFrom[From = Nothing, _, _]. Вот как cc.to[Set]это выражается, где a Setстроится без учета исходной коллекции cc. Для таких операций, как map, метод collection.breakOutобеспечивает такой CanBuildFrom, где тип результата может быть с пользой выведен.

Вы можете ввести произвольное CanBuildFromдля желаемого поведения:

$ scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_92).
Type in expressions for evaluation. Or try :help.

scala> val m = Map("a" -> 1, "b" -> 1)
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 1)

scala> val k = m.keys
k: Iterable[String] = Set(a, b)

scala> import collection.{generic, mutable}, generic.{CanBuildFrom => CBF}, mutable.ListBuffer
import collection.{generic, mutable}
import generic.{CanBuildFrom=>CBF}
import mutable.ListBuffer

scala>   implicit def `as list`: CBF[Iterable[_], Int, List[Int]] =
     |     new CBF[Iterable[_], Int, List[Int]] {
     |       def apply() = new ListBuffer[Int]
     |       def apply(from: Iterable[_]) = apply()
     |     }
as$u0020list: scala.collection.generic.CanBuildFrom[Iterable[_],Int,List[Int]]

scala> k.map(m)
res0: List[Int] = List(1, 1)

Стоит добавить, что завершение может показывать типы по состоянию на 2.11.8:

scala> k.map(m) //print<tab>

$line4.$read.$iw.$iw.k.map[Int, Iterable[Int]]($line3.$read.$iw.$iw.m)(scala.collection.Iterable.canBuildFrom[Int]) // : Iterable[Int]

Использование breakOut:

scala> k.map(m)(collection.breakOut)
res1: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 1)

scala> k.map(m)(collection.breakOut) //print

$line4.$read.$iw.$iw.k.map[Int, scala.collection.immutable.IndexedSeq[Int]]($line3.$read.$iw.$iw.m)(scala.collection.`package`.breakOut[Any, Int, scala.collection.immutable.IndexedSeq[Int]](scala.Predef.fallbackStringCanBuildFrom[Int])) // : scala.collection.immutable.IndexedSeq[Int]

Как показано, он фактически выбирает CanBuildFromпредназначенные для таких операций, как:

scala> "abc".map(_ + 1)
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(98, 99, 100)

scala> "abc".map(_ + 1) //print

scala.Predef.augmentString("abc").map[Int, scala.collection.immutable.IndexedSeq[Int]](((x$1: Char) => x$1.+(1)))(scala.Predef.fallbackStringCanBuildFrom[Int]) // : scala.collection.immutable.IndexedSeq[Int]

Для сравнения:

scala> k.map(m)(collection.breakOut) : List[Int] //print

(($line6.$read.$iw.$iw.k.map[Int, List[Int]]($line5.$read.$iw.$iw.m)(scala.collection.`package`.breakOut[Iterable[String], Int, List[Int]](scala.collection.immutable.List.canBuildFrom[Int]))): scala.`package`.List[scala.Int]) // : List[Int]

Каноническое Q & А на BreakOut .

Автор: som-snytt Размещён: 21.08.2016 03:21

5 плюса

foo.keysвозвращает a Set(несмотря на более общий тип возвращаемого значения) и вызов map для a Setсоздает другой Set. Предполагаемый тип или тип времени компиляции не всегда самый точный.

Вы можете видеть , что keysметод на Setвозвращает Setдаже если возвращение типа является Iterable[A]:

scala> Map(1 -> 2).keys
res0: Iterable[Int] = Set(1)
Автор: Kim Stebel Размещён: 20.08.2016 04:10

0 плюса

Это хитрая неявная магия. Упрощенный ответ: существует CanBuildFromзначение, которое передается в неявной области видимости. Когда компилятор ищет наиболее распространенный тип, он ищет следствия в области аргументов.

В вашем примере компилятор может выяснить, что наиболее распространенный тип для foo.keys- это Set. Это звучит разумно: Set можно рассматривать как Map с отсутствующими значениями (также это делают Java-HashMap / HashSet). Когда вы преобразуетесь в итеративный, последствия теряются и исчезают Set(как примечание: эти CanBuildFromхаки не являются надежными и могут исчезнуть в будущем, поскольку они действительно усложняют расширение существующих коллекций, вы также можете прочитать этот ответ и комментарии ).

Scala разделяет концепцию Java «де-юре и де-факто типы». «де-юре» - это тот, который объявлен в определении метода, но «де-факто» может быть одним из наследников. Вот почему, например, вы видите Map.keysтип как Iterable, когда он де-факто Set(получен из Map.keySet, который имеет Setтип де-юре).

Последнее, 1в первом println, потому что в базовой карте fooвсе значения одинаковы, и Set(1,1)становится Set(1).

Автор: dveim Размещён: 20.08.2016 04:42
Вопросы из категории :
32x32