英文:
Why is the implementation of the map function of Collection inconsistent with the actual
问题
我最近阅读了Swift源代码,并看到了Collection的map
函数的实现如下:
@inlinable
public func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
// TODO: swift-3-indexing-model - review the following
let n = self.count
if n == 0 {
return []
}
var result = ContiguousArray<T>()
result. reserveCapacity(n)
var i = self. startIndex
for _ in 0..<n {
result.append(try transform(self[i]))
formIndex(after: &i)
}
_expectEnd(of: self, is: i)
return Array(result)
}
看起来map
函数会按照自身的大小依次访问i下标元素,并依次调用变换闭包参数。
我编写了以下代码进行验证,并发现它与Collection源代码实现不同:
var array = [0, 1, 2]
array.map { index -> Void in
array[2] = -1
print("\(index)")
}
根据Collection源代码,这段代码的打印结果应该是:
0
1
-1
但实际上打印出了:
0
1
2
我想知道为什么这段代码与源代码的行为不一致。
更新:
看来我的示例代码中存在一些误解。实际上,我知道map
的正确用法,我只是想验证我对源代码的理解。
为了准确性,我决定稍微修改我的示例代码:
var array = [0, 1, 2]
let newArray = array. map { element -> Int in
array[element] = -1
return element
}
print(array)
print(newArray)
现在这段代码正确使用了map
,并且在map
中修改了数组的内容,并返回了element
以形成newArray
,打印的结果如下:
[-1, -1, -1]
[0, 1, 2]
英文:
I recently read the Swift source code and saw that the map
function of Collection is implemented as follows
@inlinable
public func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
// TODO: swift-3-indexing-model - review the following
let n = self.count
if n == 0 {
return []
}
var result = ContiguousArray<T>()
result. reserveCapacity(n)
var i = self. startIndex
for _ in 0..<n {
result.append(try transform(self[i]))
formIndex(after: &i)
}
_expectEnd(of: self, is: i)
return Array(result)
}
It seems that the map
function will visit the i subscript elements in turn according to its own size, and call the transform closure parameter one by one.
I wrote the following code to verify, and found that it is different from the Collection source code implementation
var array = [0, 1, 2]
array.map { index -> Void in
array[2] = -1
print("\(index)")
}
According to the Collection source code, the print result of this code should be
0
1
-1
but actually it prints
0
1
2
I'm wondering why this code behaves inconsistently with the source code.
Update:
It looks like there is some misunderstanding in my example code. Actually I know the correct usage of map
, I just want to verify my understanding of the source code.
So for the sake of accuracy, I decided to modify my example code a bit
var array = [0, 1, 2]
let newArray = array. map { element -> Int in
array[element] = -1
return element
}
print(array)
print(newArray)
Now this code uses map
correctly, and I modify the content of array in map, and return element
to form newArray
, the printed result is as follows
[-1, -1, -1]
[0, 1, 2]
答案1
得分: 2
让我用一种我认为不会引起争议的更简单的方式来重写这段代码,然后希望你能明白你的代码与之等效:
// 一个接受数组和转换的自由函数
func iterate(over: [Int], _ transform: (Int) -> Void) {
let n = over.count
var i = over.startIndex
for _ in 0..<n {
transform(over[i])
over.formIndex(after: &i)
}
}
var array = [0, 1, 2]
iterate(over: array) { value in
array[2] = -1
print(value)
}
print(array)
这将打印与你的示例相同的结果:
0
1
2
[0, 1, -1]
这正是在Swift中所期望的。over
是 array
的一个副本,所以 array[2] = -1
与 over
无关。值类型参数总是副本(即使 inout
也是副本;它们只在最后被复制回去)。这个行为希望从中变得清晰。
这个和你的示例唯一的不同之处在于它被调用为一个函数而不是一个方法,但这根本没有区别。在这段代码中,over
和 self
扮演着完全相同的角色,并且表现出完全相同的方式。self
是一个副本。为了演示,这是将接受 Array
的函数机械转换为 Array
扩展的过程:
extension Array<Int> {
func iterate(_ transform: (Int) -> Void) {
let n = self.count
var i = self.startIndex
for _ in 0..<n {
transform(self[i])
self.formIndex(after: &i)
}
}
}
var array = [0, 1, 2]
array.iterate { value in
array[2] = -1
print(value)
}
print(array)
它的行为完全相同(正如所期望的)。将参数的名称从 over
改为 self
不会改变行为。map
也是一样的。它等同于这个(内联方法):
var array = [0, 1, 2]
let `self` = array // 这会创建一个副本;使用 `let` 因为它是不可变的
let n = `self`.count
var i = `self`.startIndex
for _ in 0..<n {
array[2] = -1
print(`self`[i])
`self`.formIndex(after: &i)
}
print(array)
关于这个问题,可以参考《The Swift Programming Language》中的Structures and Enumerations Are Value Types:
事实上,Swift 中的所有基本类型 — 整数、浮点数、布尔值、字符串、数组和字典 — 都是值类型,并且在幕后都以结构体的形式实现。
在Swift中,所有结构体和枚举类型都是值类型。这意味着你创建的任何结构体和枚举实例,以及它们作为属性的值类型,都会在代码中传递时进行复制。
英文:
Let me rewrite this in a simpler way that I believe will be uncontroversial, and then hopefully it will be clear that your code is equivalent:
// A free function that accepts an array and a transform
func iterate(over: [Int], _ transform: (Int) -> Void) {
let n = over.count
var i = over.startIndex
for _ in 0..<n {
transform(over[i])
over.formIndex(after: &i)
}
}
var array = [0, 1, 2]
iterate(over: array) { value in
array[2] = -1
print(value)
}
print(array)
This will print the same as your example:
0
1
2
[0, 1, -1]
This is exactly as should be expected in Swift. over
is a copy of array
, so array[2] = -1
has nothing to do with over
. Value-type parameters always are copies (even inout
are copies; they just get copied back at the end). The behavior hopefully is clear from that.
The only difference between this and your example is that it is called as a function rather than a method, but that is no difference at all. over
and self
play precisely the same roles in this code and behave exactly the same way. self
is a copy. To demonstrate, this is a mechanical conversion of the function taking Array
to an extension on Array
:
extension Array<Int> {
func iterate(_ transform: (Int) -> Void) {
let n = self.count
var i = self.startIndex
for _ in 0..<n {
transform(self[i])
self.formIndex(after: &i)
}
}
}
var array = [0, 1, 2]
array.iterate { value in
array[2] = -1
print(value)
}
print(array)
It behaves identically (as should be expected). Changing the parameter's name from over
to self
does not change the behavior. map
is the same. It's equivalent to this (inlining the method):
var array = [0, 1, 2]
let `self` = array // This makes a copy; `let` because it's non-mutating
let n = `self`.count
var i = `self`.startIndex
for _ in 0..<n {
array[2] = -1
print(`self`[i])
`self`.formIndex(after: &i)
}
print(array)
For more on this, see Structures and Enumerations Are Value Types in The Swift Programming Language:
>In fact, all of the basic types in Swift — integers, floating-point numbers, Booleans, strings, arrays and dictionaries — are value types, and are implemented as structures behind the scenes.
>
>All structures and enumerations are value types in Swift. This means that any structure and enumeration instances you create — and any value types they have as properties — are always copied when they’re passed around in your code.
答案2
得分: 1
你在示例中实际上并没有正确使用map
函数,因此不清楚你试图测试什么。但如果你添加了进一步的语句print(array)
,你将会看到原始的array
确实发生了变化。
var array = [0, 1, 2]
array.map { index -> Void in
array[2] = -1
print("\(index)")
}
print(array) // [0, 1, -1]
你对array[2] = -1
的调用并不会影响我们正在循环遍历的数组(从中获取了你命名不当的index
),因为它是原始array
的一个 副本。
英文:
You are not actually using the map
function correctly in your example, so it's unclear what you're trying to test. But if you append a further statement print(array)
you will see that the original array
was indeed changed.
var array = [0, 1, 2]
array.map { index -> Void in
array[2] = -1
print("\(index)")
}
print(array) // [0, 1, -1]
Your call to array[2] = -1
does not affect the array that we are looping over (and from which you are getting your poorly named index
), because it is a copy of the original array
.
答案3
得分: 0
你得到了一个不一致的结果,因为实际上你根本没有使用map
功能。
map
的签名
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
意味着以下内容:
- 它不会原地修改数组,而是返回一个包含转换后元素的新数组。
- 在闭包
(Element) throws -> T
中,你会获得每个元素 - 顺便说一下,这是元素的不可变副本 - 你必须返回修改/转换后的值。
index -> Void
是无意义的,因为你在闭包中没有返回任何东西,相反,你原地修改了数组,实际上是将数组的第三个元素三次覆盖为-1。此外,index
不是索引,它是元素。如果数组是[2, 4, 6]
,那么index
是2, 4, 6
,而不是0, 1, 2
。
如果你想要使用map
将值为2的元素设置为-1,你应该这样写:
let result = array.map { element -> Int in
if element == 2 {
return -1
} else {
return element
}
}
result
将会是 [0, 1, -1]
,而 array
保持不变。
但是 map
的真正目的是像这样的转换:
let result = array.map { element -> String in
return "\(element * 2) times"
}
现在 result
是 ["0 times", "2 times", "4 times"]
。
泛型的魔力在于你甚至不需要指定返回类型,这已经足够了:
let result = array.map { "\($0 * 2) times" }
英文:
You get an inconsistent result because actually you don't use the map
functionality at all.
The signature of map
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
means the following:
- It does not modify the array in place, it returns a new array with the transformed elements.
- In the closure
(Element) throws -> T
you get each element – which is an immutable copy of the element by the way – and you have to return the modified/transformed value.
index -> Void
is pointless because you don't return anything in the closure, instead you modify the array in place, in practice you overwrite the 3rd element of the array with -1 three times. Further index
is not the index, it's the element. If the array is [2, 4, 6]
then index
is 2, 4, 6
not 0, 1, 2
.
If you want to set the element with value 2 to -1 using map
you have to write
let result = array.map { element -> Int in
if element == 2 {
return -1
} else {
return element
}
}
result
will be [0, 1, -1]
, on the other hand array
remains unchanged.
But the real purpose of map
is a transformation like
let result = array.map { element -> String in
return "\(element * 2) times"
}
Now result
is ["0 times", "2 times", "4 times"]
The magic of generics is that you don't even need to specify the return type, this is sufficient
let result = array.map { "\($0 * 2) times" }
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论