来自Java背景,我正在学习Scala,以下内容使我感到非常困惑。为什么在这两个(非常相似但不同)的构造中返回的类型不同,它们仅在构建源集合的方式上有所不同-
val seq1: IndexedSeq[Int] = for (i <- 1 to 10) yield i
与
val seq2: Array[Int] = for (i <- Array(1, 2, 3)) yield i
请为我指出正确的文献,以便在这里我可以理解核心基础。
来自Java背景,我正在学习Scala,以下内容使我感到非常困惑。为什么在这两个(非常相似但不同)的构造中返回的类型不同,它们仅在构建源集合的方式上有所不同-
val seq1: IndexedSeq[Int] = for (i <- 1 to 10) yield i
与
val seq2: Array[Int] = for (i <- Array(1, 2, 3)) yield i
请为我指出正确的文献,以便在这里我可以理解核心基础。
发生这种情况是因为构造:
是相同的:
for () yield
is just syntactic sugar forflatMap
/map
function.As we know
map
function doesn't change type of object-container, it changes element inside container. So, in your example1 to 10
has a typeRange.Inclusive
which extendsRange
, andRange
extendsIndexedSeq
. MappedIndexedSeq
has the same typeIndexedSeq
.for (i <- 1 to 10) yield i
the same as(1 to 10).map(x => x)
In second case:
for (i <- Array(1, 2, 3)) yield i
you haveArray
, and mappedArray
type alsoArray
.for (i <- Array(1, 2, 3)) yield i
the same asArray(1, 2, 3).map(x => x)
通常,有两种不同样式的收集操作库:
Type-preserving collection operations try to preserve the type exactly for operations like
filter
,take
,drop
, etc. that only take existing elements unmodified. For operations likemap
, it tries to find the closest super type that can still hold the result. E.g. mapping over anIntSet
with a function fromInt
toString
can obviously not result in anIntSet
, but only in aSet
. Mapping anIntSet
toBoolean
could be represented in aBitSet
, but I know of no collections framework that is clever enough to actually do that.Generic / homogeneous collection operations always return the same type. Usually, this type is chosen to be very general, to accommodate the widest range of use cases. For example, In .NET, collection operations return
IEnumerable
, in Java, they returnStream
s, in C++, they return iterators, in Ruby, they return arrays.Until recently, it was only possible to implement type-preserving collection operations by duplicating all operations for all types. For example, the Smalltalk collections framework is type-preserving, and it does this by having every single collections class re-implement every single collections operation. This results in a lot of duplicated code and is a maintenance nightmare. (It is no coincidence that many new object-oriented abstractions that get invented have their first paper written about how it can be applied to the Smalltalk collections framework. See Traits: Composable Units of Behaviour for an example.)
To my knowledge, the Scala 2.8 re-design of the collections framework (see also this answer on SO) was the first time someone managed to create type-preserving collections operations while minimizing (though not eliminating) duplication. However, the Scala 2.8 collections framework was widely criticized as being overly complex, and it has required constant work over the last decade. In fact, it actually lead to a complete re-design of the Scala documentation system as well, just to be able to hide the very complex type signatures that the type-preserving operations require. But, this still wasn't enough, so the collections framework was completely thrown out and re-designed yet again in Scala 2.13. (And this re-design took several years.)
因此,对您的问题的简单回答是:Scala尝试尽可能保留集合的类型。
In your second case, the type of the collection is
Array
, and when youmap
over anArray
, you get back anArray
.In your first case, the type of the collection is
Range
. Now, aRange
doesn't actually have elements, though. It only has a beginning and an end and a step, and it produces the elements on demand while you are iterating over it. So, it is not that easy to produce a newRange
with the new elements. Themap
function would basically need to be able to "reverse engineer" your mapping function to figure out what the new beginning and end and step should be. (Which is equivalent to solving the Halting Problem, or in other words impossible.) And what if you do something like this:Here, there isn't even a well-defined step, so it is actually impossible to build a
Range
that does this.So, clearly, mapping over a
Range
cannot return aRange
. So, it does the next best thing: It returns the most precise super type ofRange
that can contain the mapped values. In this case, that happens to beIndexedSeq
.There is a wrinkle, in that type-preserving collections operations challenge what we consider to be part of the contract of certain operations. For example, most people would argue that the cardinality of a collection should be invariant under
map
, in other words,map
should map each element to exactly one new element and thusmap
should never change the size of the collection. But, what about this code:Here, you get back a collection with fewer elements from a
map
, which is only supposed to transform elements, not remove them. But, since we decided we want type-preserving collections, and aSet
cannot have duplicate values, the twofalse
values are actually the same value, so there is only one of them in the set.[It could be argued that this actually only demonstrates that
Set
s aren't collections and shouldn't be treated as collections.Set
s are predicates ("Is this element a member?") rather than collections ("Give me all your elements!")]