2

I am attempting to extend Array<MutatingCollection> so I can mirror the contents of an Array of Arrays, but the compiler says I can't call reverse() on the elements in the array, despite reverse() being defined in MutatingCollection protocol.

I want to do something like this:

var table = [[0,1,2],
             [3,4,5],
             [6,7,8]]
table.mirror()
//table now [[2,1,0],
//           [5,4,3],
//           [8,7,6]]

Here is my (not working) code:

 extension Array where Element == MutableCollection {
        mutating func mirror() {
            for index in self.indices {
                self[index].reverse()
            }
        }
    }

I have tried it as self.map {array in array.reverse()} as well (which I think does the same thing, but I don't fully grok map()) Both ways result in the same error message:

Member 'reverse' cannot be used on value of type 'MutableCollection'

Edit: I can call the same code directly and it works as I intended.

Playgrounds Screenshot

Perhaps I'm using extension improperly, or Swift Playgrounds is blocking my access somehow.

1 Answers1

1

First of all, the extension should be declared like this:

extension Array where Element : MutableCollection {

You want to check that Element adheres to the protocol MutableCollection, not that it is a MutableCollection

However, then I'm not able to call the reverse method on the subscript for some reason. The best I've been able to do is this:

extension Array where Element : MutableCollection {
  mutating func mirror() {
    for index in self.indices {
      self[index] = self[index].reversed() as! Element
    }
  }
}

Which works as you need it to work although the forced cast is very ugly and I dislike doing it. I suppose I should test the cast to be certain but I can't see any case where calling reversed() would result in a collection that couldn't be cast back to Element.

Edit:

I figured out the issue. The reverse() method is only valid on MutableCollection when it is also a BidirectionalCollection. This code now works correctly:

extension MutableCollection where
  Iterator.Element : MutableCollection &
                     BidirectionalCollection,
  Indices.Iterator.Element == Index {
  mutating func mirror() {
    for index in self.indices {
      self[index].reverse()
    }
  }
}

Now the code should work for all MutableCollection whose elements are both a MutableCollection and BidirectionalCollection - such as [Array<Int>] or even [ArraySlice<Int>]

You can see the full code for reverse() in Swift 3.1 here:

Reverse.swift

extension MutableCollection where Self : BidirectionalCollection

  • This crashes if `Element` is a mutable collection but not an array, e.g. for `var table = [[0,1,2].dropFirst(), [3,4,5], [6,7,8]]`, where `Element` is `ArraySlice`. – Martin R Jun 27 '17 at 03:09
  • @MartinR I believe I've nailed down the cause for the issue posed in this question, as well as correcting for the issue you mentioned. Please feel free to check its correctness for me. –  Jun 27 '17 at 13:08
  • 1
    With an additional constraint `Indices.Iterator.Element == Index` you can get rid of the artificial `guard let index = index ...`, compare https://stackoverflow.com/a/44457590/1187415 or https://stackoverflow.com/a/40331858/1187415. It should not be necessary in Swift 4. – Martin R Jun 27 '17 at 13:24
  • @MartinR Perfect! I was looking for the best way to achieve that. Thanks. –  Jun 27 '17 at 13:30
  • Thanks to both of you! – Christopher H. Jun 29 '17 at 16:58