Coming from a Java background I am learning Scala and the following has me very confused. Why is the type returned different in these two (very similar but different) constructs, which vary only in how the source collection was build -
val seq1: IndexedSeq[Int] = for (i <- 1 to 10) yield i
Run Code Online (Sandbox Code Playgroud)
vs.
val seq2: Array[Int] = for (i <- Array(1, 2, 3)) yield i
Run Code Online (Sandbox Code Playgroud)
请务必向我指出正确的文献,以便我能够理解此处起作用的核心基础知识。
发生这种情况是因为构造:
for (x <-someSeq) yield x
Run Code Online (Sandbox Code Playgroud)
是相同的:
someSeq.map(x => x)
Run Code Online (Sandbox Code Playgroud)
for () yield只是flatMap/map函数的语法糖。
我们知道map函数不会改变对象容器的类型,它会改变容器内的元素。因此,在您的示例中,1 to 10有一个类型Range.InclusiveextendsRange和Rangeextends IndexedSeq。MappedIndexedSeq具有相同的类型IndexedSeq。
for (i <- 1 to 10) yield i 一样 (1 to 10).map(x => x)
在第二种情况下:for (i <- Array(1, 2, 3)) yield i您也有Array, 和映射Array类型Array。
for (i <- Array(1, 2, 3)) yield i 一样 Array(1, 2, 3).map(x => x)
一般来说,集合操作库有两种不同的风格:
类型保持收集操作尽量准确地保存类型操作,例如filter,take,drop等,仅利用现有未经修改的元素。对于像 这样的操作map,它会尝试找到最接近的仍然可以保存结果的超类型。例如IntSet,用函数 from Intto映射 anString显然不会产生 an IntSet,而只能产生 a Set。将 an 映射IntSet到Boolean可以用 a 表示BitSet,但我知道没有足够聪明的集合框架来实际做到这一点。
泛型/同类集合操作总是返回相同的类型。通常,选择这种类型是非常通用的,以适应最广泛的用例。例如,在 .NET 中,集合操作返回IEnumerable,在 Java 中,它们返回Streams,在 C++ 中,它们返回迭代器,在 Ruby 中,它们返回数组。
直到最近,只能通过复制所有类型的所有操作来实现类型保留的集合操作。例如,Smalltalk 集合框架是类型保留的,它通过让每个集合类重新实现每个集合操作来实现这一点。这会导致大量重复的代码,并且是维护的噩梦。(许多被发明的新的面向对象抽象都写了第一篇关于如何将其应用于 Smalltalk 集合框架的论文,这并非巧合。请参阅特征:行为的可组合单元。)
据我所知,Scala 2.8 对集合框架的重新设计(另请参阅 SO 上的这个答案)是第一次有人设法创建保留类型的集合操作,同时最大限度地减少(尽管不是消除)重复。然而,Scala 2.8 集合框架被广泛批评为过于复杂,并且在过去十年中需要不断努力。事实上,它实际上也导致了 Scala 文档系统的完全重新设计,只是为了能够隐藏类型保留操作所需的非常复杂的类型签名。但是,这还不够,所以集合框架被彻底抛弃并重新设计再次在 Scala 2.13 中。(而这种重新设计花了几年时间。)
因此,对您的问题的简单回答是:Scala 尽可能地尝试保留集合的类型。
在你的第二种情况下,集合的类型是Array,当你map超过一个时Array,你会得到一个Array.
在您的第一种情况下,集合的类型是Range. 不过,现在 aRange实际上没有元素。它只有一个开始、一个结束和一个步骤,并且在您对其进行迭代时按需生成元素。因此,Range用新元素制作新作品并不容易。该map函数基本上需要能够“逆向工程”您的映射函数,以确定新的开始和结束以及步骤应该是什么。(这相当于解决停机问题,或者换句话说不可能。)如果你做这样的事情会怎样:
val seq1: IndexedSeq[Int] = for (i <- 1 to 10) yield scala.util.Random.nextInt(i)
Run Code Online (Sandbox Code Playgroud)
在这里,甚至没有一个明确定义的步骤,所以实际上不可能构建一个Range这样做的。
因此,显然,对 a 的映射Range不能返回 a Range。所以,它做了下一件最好的事情:它返回可以包含映射值的最精确的超类型Range。在这种情况下,恰好是IndexedSeq。
有一个问题,即类型保留的集合操作挑战了我们认为是某些操作合同的一部分。例如,大多数人会争辩说,集合的基数在 下应该是不变的map,换句话说,map应该将每个元素映射到一个新元素,因此map永远不应该改变集合的大小。但是,这段代码呢:
Set(1, 2, 3).map { _ % 2 == 0 }
//=> Set(true, false)
Run Code Online (Sandbox Code Playgroud)
在这里,您从 a 中返回一个元素较少的集合,map它只应该转换元素,而不是删除它们。但是,由于我们决定要保留类型的集合,并且 aSet不能有重复的值,因此这两个false值实际上是相同的值,因此集合中只有其中一个。
[可以说,这实际上只是表明Sets 不是集合,不应被视为集合。Sets 是谓词(“这个元素是成员吗?”)而不是集合(“给我你所有的元素!”)]