为什么Collection的map函数的实现与实际情况不一致?

huangapple go评论97阅读模式
英文:

Why is the implementation of the map function of Collection inconsistent with the actual

问题

我最近阅读了Swift源代码,并看到了Collection的map函数的实现如下:

  1. @inlinable
  2. public func map<T>(
  3. _ transform: (Element) throws -> T
  4. ) rethrows -> [T] {
  5. // TODO: swift-3-indexing-model - review the following
  6. let n = self.count
  7. if n == 0 {
  8. return []
  9. }
  10. var result = ContiguousArray<T>()
  11. result. reserveCapacity(n)
  12. var i = self. startIndex
  13. for _ in 0..<n {
  14. result.append(try transform(self[i]))
  15. formIndex(after: &i)
  16. }
  17. _expectEnd(of: self, is: i)
  18. return Array(result)
  19. }

看起来map函数会按照自身的大小依次访问i下标元素,并依次调用变换闭包参数。

我编写了以下代码进行验证,并发现它与Collection源代码实现不同:

  1. var array = [0, 1, 2]
  2. array.map { index -> Void in
  3. array[2] = -1
  4. print("\(index)")
  5. }

根据Collection源代码,这段代码的打印结果应该是:

  1. 0
  2. 1
  3. -1

但实际上打印出了:

  1. 0
  2. 1
  3. 2

我想知道为什么这段代码与源代码的行为不一致。


更新:

看来我的示例代码中存在一些误解。实际上,我知道map的正确用法,我只是想验证我对源代码的理解。

为了准确性,我决定稍微修改我的示例代码:

  1. var array = [0, 1, 2]
  2. let newArray = array. map { element -> Int in
  3. array[element] = -1
  4. return element
  5. }
  6. print(array)
  7. print(newArray)

现在这段代码正确使用了map,并且在map中修改了数组的内容,并返回了element以形成newArray,打印的结果如下:

  1. [-1, -1, -1]
  2. [0, 1, 2]
英文:

I recently read the Swift source code and saw that the map function of Collection is implemented as follows

  1. @inlinable
  2. public func map<T>(
  3. _ transform: (Element) throws -> T
  4. ) rethrows -> [T] {
  5. // TODO: swift-3-indexing-model - review the following
  6. let n = self.count
  7. if n == 0 {
  8. return []
  9. }
  10. var result = ContiguousArray<T>()
  11. result. reserveCapacity(n)
  12. var i = self. startIndex
  13. for _ in 0..<n {
  14. result.append(try transform(self[i]))
  15. formIndex(after: &i)
  16. }
  17. _expectEnd(of: self, is: i)
  18. return Array(result)
  19. }

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

  1. var array = [0, 1, 2]
  2. array.map { index -> Void in
  3. array[2] = -1
  4. print("\(index)")
  5. }

According to the Collection source code, the print result of this code should be

  1. 0
  2. 1
  3. -1

but actually it prints

  1. 0
  2. 1
  3. 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

  1. var array = [0, 1, 2]
  2. let newArray = array. map { element -> Int in
  3. array[element] = -1
  4. return element
  5. }
  6. print(array)
  7. 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, -1]
  2. [0, 1, 2]

答案1

得分: 2

让我用一种我认为不会引起争议的更简单的方式来重写这段代码,然后希望你能明白你的代码与之等效:

  1. // 一个接受数组和转换的自由函数
  2. func iterate(over: [Int], _ transform: (Int) -> Void) {
  3. let n = over.count
  4. var i = over.startIndex
  5. for _ in 0..<n {
  6. transform(over[i])
  7. over.formIndex(after: &i)
  8. }
  9. }
  10. var array = [0, 1, 2]
  11. iterate(over: array) { value in
  12. array[2] = -1
  13. print(value)
  14. }
  15. print(array)

这将打印与你的示例相同的结果:

  1. 0
  2. 1
  3. 2
  4. [0, 1, -1]

这正是在Swift中所期望的。overarray 的一个副本,所以 array[2] = -1over 无关。值类型参数总是副本(即使 inout 也是副本;它们只在最后被复制回去)。这个行为希望从中变得清晰。

这个和你的示例唯一的不同之处在于它被调用为一个函数而不是一个方法,但这根本没有区别。在这段代码中,overself 扮演着完全相同的角色,并且表现出完全相同的方式。self 是一个副本。为了演示,这是将接受 Array 的函数机械转换为 Array 扩展的过程:

  1. extension Array<Int> {
  2. func iterate(_ transform: (Int) -> Void) {
  3. let n = self.count
  4. var i = self.startIndex
  5. for _ in 0..<n {
  6. transform(self[i])
  7. self.formIndex(after: &i)
  8. }
  9. }
  10. }
  11. var array = [0, 1, 2]
  12. array.iterate { value in
  13. array[2] = -1
  14. print(value)
  15. }
  16. print(array)

它的行为完全相同(正如所期望的)。将参数的名称从 over 改为 self 不会改变行为。map 也是一样的。它等同于这个(内联方法):

  1. var array = [0, 1, 2]
  2. let `self` = array // 这会创建一个副本;使用 `let` 因为它是不可变的
  3. let n = `self`.count
  4. var i = `self`.startIndex
  5. for _ in 0..<n {
  6. array[2] = -1
  7. print(`self`[i])
  8. `self`.formIndex(after: &i)
  9. }
  10. 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:

  1. // A free function that accepts an array and a transform
  2. func iterate(over: [Int], _ transform: (Int) -&gt; Void) {
  3. let n = over.count
  4. var i = over.startIndex
  5. for _ in 0..&lt;n {
  6. transform(over[i])
  7. over.formIndex(after: &amp;i)
  8. }
  9. }
  10. var array = [0, 1, 2]
  11. iterate(over: array) { value in
  12. array[2] = -1
  13. print(value)
  14. }
  15. print(array)

This will print the same as your example:

  1. 0
  2. 1
  3. 2
  4. [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:

  1. extension Array&lt;Int&gt; {
  2. func iterate(_ transform: (Int) -&gt; Void) {
  3. let n = self.count
  4. var i = self.startIndex
  5. for _ in 0..&lt;n {
  6. transform(self[i])
  7. self.formIndex(after: &amp;i)
  8. }
  9. }
  10. }
  11. var array = [0, 1, 2]
  12. array.iterate { value in
  13. array[2] = -1
  14. print(value)
  15. }
  16. 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):

  1. var array = [0, 1, 2]
  2. let `self` = array // This makes a copy; `let` because it&#39;s non-mutating
  3. let n = `self`.count
  4. var i = `self`.startIndex
  5. for _ in 0..&lt;n {
  6. array[2] = -1
  7. print(`self`[i])
  8. `self`.formIndex(after: &amp;i)
  9. }
  10. 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确实发生了变化。

  1. var array = [0, 1, 2]
  2. array.map { index -> Void in
  3. array[2] = -1
  4. print("\(index)")
  5. }
  6. 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.

  1. var array = [0, 1, 2]
  2. array.map { index -&gt; Void in
  3. array[2] = -1
  4. print(&quot;\(index)&quot;)
  5. }
  6. 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的签名

  1. func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

意味着以下内容:

  • 不会原地修改数组,而是返回一个包含转换后元素的新数组。
  • 在闭包(Element) throws -> T中,你会获得每个元素 - 顺便说一下,这是元素的不可变副本 - 你必须返回修改/转换后的值。

index -> Void 是无意义的,因为你在闭包中没有返回任何东西,相反,你原地修改了数组,实际上是将数组的第三个元素三次覆盖为-1。此外,index不是索引,它是元素。如果数组是[2, 4, 6],那么index2, 4, 6,而不是0, 1, 2

如果你想要使用map将值为2的元素设置为-1,你应该这样写:

  1. let result = array.map { element -> Int in
  2. if element == 2 {
  3. return -1
  4. } else {
  5. return element
  6. }
  7. }

result 将会是 [0, 1, -1],而 array 保持不变。

但是 map真正目的是像这样的转换:

  1. let result = array.map { element -> String in
  2. return "\(element * 2) times"
  3. }

现在 result["0 times", "2 times", "4 times"]

泛型的魔力在于你甚至不需要指定返回类型,这已经足够了:

  1. 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

  1. func map&lt;T&gt;(_ transform: (Element) throws -&gt; T) rethrows -&gt; [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 -&gt; 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 -&gt; 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

  1. let result = array.map { element -&gt; Int in
  2. if element == 2 {
  3. return -1
  4. } else {
  5. return element
  6. }
  7. }

result will be [0, 1, -1], on the other hand array remains unchanged.

But the real purpose of map is a transformation like

  1. let result = array.map { element -&gt; String in
  2. return &quot;\(element * 2) times&quot;
  3. }

Now result is [&quot;0 times&quot;, &quot;2 times&quot;, &quot;4 times&quot;]

The magic of generics is that you don't even need to specify the return type, this is sufficient

  1. let result = array.map { &quot;\($0 * 2) times&quot; }

huangapple
  • 本文由 发表于 2023年6月12日 20:37:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76456743.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定