В фреймворке Scala Collections я думаю, что при использовании map()
.
существуют некоторые поведения, которые противоречат друг другу. Мы можем различать два вида преобразований в (неизменяемых) коллекциях.Те, чья реализация вызывает newBuilder
для воссоздания результирующей коллекции, и те, кто идет неявным CanBuildFrom
, чтобы получить конструктор.
Первая категория содержит все преобразования, в которых тип содержащихся элементов не изменяется,Это, например, filter
, partition
, drop
, take
, span
и т. Д. Эти преобразования могут вызывать newBuilder
и воссоздавать тот же тип коллекции, что и тот, который они называютнезависимо от того, насколько конкретно: фильтрация List[Int]
всегда может вернуть List[Int]
;фильтрация BitSet
(или пример структуры RNA
, описанной в этой статье об архитектуре инфраструктуры коллекций ) всегда может вернуть другое BitSet
(или RNA
).Давайте назовем их фильтрующими преобразованиями .
Второй категории преобразований требуется CanBuildFrom
s, чтобы быть более гибкими, поскольку тип содержащихся элементов может измениться, и в результате этоготип самой коллекции, возможно, не может быть повторно использован: a BitSet
не может содержать String
s;RNA
содержит только Base
с.Примерами таких преобразований являются map
, flatMap
, collect
, scanLeft
, ++
и т. Д. Давайте назовем их преобразованиями отображения .
Теперь вот основныевопрос для обсуждения.Независимо от того, какой статический тип коллекции, все преобразования фильтрации будут возвращать один и тот же тип коллекции, в то время как тип коллекции, возвращаемый операцией отображения, может варьироваться в зависимости от статического типа.
scala> import collection.immutable.TreeSet
import collection.immutable.TreeSet
scala> val treeset = TreeSet(1,2,3,4,5) // static type == dynamic type
treeset: scala.collection.immutable.TreeSet[Int] = TreeSet(1, 2, 3, 4, 5)
scala> val set: Set[Int] = TreeSet(1,2,3,4,5) // static type != dynamic type
set: Set[Int] = TreeSet(1, 2, 3, 4, 5)
scala> treeset.filter(_ % 2 == 0)
res0: scala.collection.immutable.TreeSet[Int] = TreeSet(2, 4) // fine, a TreeSet again
scala> set.filter(_ % 2 == 0)
res1: scala.collection.immutable.Set[Int] = TreeSet(2, 4) // fine
scala> treeset.map(_ + 1)
res2: scala.collection.immutable.SortedSet[Int] = TreeSet(2, 3, 4, 5, 6) // still fine
scala> set.map(_ + 1)
res3: scala.collection.immutable.Set[Int] = Set(4, 5, 6, 2, 3) // uh?!
Теперь японять, почему это работает такОбъясняется там и там .Вкратце: неявный CanBuildFrom
вставляется на основе статического типа, и, в зависимости от реализации его метода def apply(from: Coll)
, может или не сможет воссоздать тот же тип коллекции.
Теперь мойЕдинственное, что, когда мы знаем, что мы используем операцию отображения, дающую коллекцию с элементом того же типа (который статически может определять компилятор), мы можем имитировать работу фильтрующих преобразований и использовать коллекциюродной строитель.Мы можем повторно использовать BitSet
при отображении на Int
с, создать новый TreeSet
с тем же порядком и т. Д.
Тогда мы бы избежали случаев, когда
for (i <- set) {
val x = i + 1
println(x)
}
невыведите увеличенные элементы TreeSet
в том же порядке, что и
for (i <- set; x = i + 1)
println(x)
Итак:
- Считаете ли вы, что было бы хорошей идеей изменить поведение отображенияпреобразований, как описано?
- Какие неизбежные предостережения я сильно упустил из виду?
- Как это можно осуществить?
Я думал о чем-то вроде implicit sameTypeEvidence: A =:= B
параметр, возможно со значением по умолчанию null
(или, скорее, implicit canReuseCalleeBuilderEvidence: B <:< A = null
), который может быть использован во время выполнения, чтобы дать больше информации для CanBuildFrom
, который, в свою очередь, может быть использован для определения типа компоновщика длявернуться.