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.
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
Related
I'm seeing something that doesn't make sense to me. Here's my code...
import Foundation
enum SeparatorPaddingLocation{
case beforeSeparator
case afterSeparator
}
struct SeparatorToken{
let separator : Character
let formattedSeparator : String
let paddingLocation : SeparatorPaddingLocation
}
extension Array where Element == String {
func aligned(separatorTokens:[SeparatorToken]) -> [String] {
// This won't compile!
let remainingSeparatorTokens = Array(separatorTokens.dropFirst())
return self
}
}
The 'let' causes this compile error even though dropFirst() returns an ArraySlice<SeparatorToken>.
Type of expression is ambiguous without more context
Why is this ambiguous? What am I missing?
I also tried fully qualifying it like so, but this didn't work either...
let remainingSeparatorTokens:[SeparatorToken] = Array(separatorTokens.dropFirst())
Inside of generic extensions, type parameters bind to their scope. I'm not sure this is really a good thing (or possibly if it's really a bug), but it shows up consistently, so the type parameter here implicitly wants to be String.
A simpler example:
let x = Array() // error: generic parameter 'Element' could not be inferred
That makes sense. But put it in an extension:
extension Array {
func f() {
let x = Array()
}
}
This compiles, and x is of type [Element].
In your case, you've set Element to String, so the call to Array.init wants to create a [String] and it's getting confused because SeparatorToken != String. You need to pass the type parameter explicitly (as you've discovered):
let remainingSeparatorTokens = Array<SeparatorToken>(separatorTokens.dropFirst())
Again, I don't really consider this a feature in Swift, and it may even be considered a bug. But it's very consistent.
I just run your code in playground and updated as follows, then error is gone:
import UIKit
import Foundation
enum SeparatorPaddingLocation{
case beforeSeparator
case afterSeparator
}
struct SeparatorToken{
let separator : Character
let formattedSeparator : String
let paddingLocation : SeparatorPaddingLocation
}
extension Array where Element == String {
func aligned(separatorTokens:[SeparatorToken]) -> [String] {
// I update code here
let remainingSeparatorTokens = Array<SeparatorToken>(separatorTokens.dropFirst())
return self
}
}
As per swift, Array required type when you going to initialise with Array.
I hope this will work for you.
For more details about array visit: https://developer.apple.com/documentation/swift/array
This is not a bug, and there's nothing mysterious or bug-like going on.
Not a bug: it's the very reason the == operator is available in where clauses: to require Element to be of type String, as explained here. Search the page for "you can also write a generic where clauses that require Item to be a specific type".
Not bug-like: when you're inside the extension and call an Array initializer, the compiler takes your where clause to indicate what specialization of Array you want. Your where clause says String, so Array<String> it is. The compiler can't read your mind when you pass an ArraySlice<SeparatorToken> into an Array<String> initializer. Did you make a mistake, or did you really want an Array<ArraySlice<SeparatorToken>>? Hence the error message about ambiguity.
Here's a simplified and annotated version of OP's code, in case anyone finds it illuminating.
class Foo<T> {
var theFoo: T!
init() { theFoo = nil }
init(_ aFoo: T) { theFoo = aFoo }
}
extension Foo where T == Int {
func bar() {
// This one fails, because our where clause
// says this extension applies to Foo<Int>,
// but now we're trying to assign it to a Foo<String>
let aFoo: Foo<String> = Foo()
// This one also fails, for the same reason.
// It uses the other initializer, but we're
// still inside the Foo<Int> extension.
let bFoo: Foo<String> = Foo("bFoo")
// This one works, because we're expressly
// overriding the where clause
let cFoo: Foo<String> = Foo<String>("cFoo")
}
}
The ambiguity error makes sense. I told the compiler in my where clause to make this an Int extension, so it created Foo<Int>s. But then I tried to assign those to variables of type Foo<String>. The compiler couldn't tell whether I really wanted Foo<String> or I had made a mistake, so it gave up.
This is the problem:
typealias Byte = UInt8
protocol ByteProtocol {}
extension UInt8: ByteProtocol {}
extension Array where Element: ByteProtocol {
subscript (index: Int) -> UInt8 {
return self[Int(index % self.count)]
}
}
This gives me Overflow even if it is mathematically impossible:
var p: [Byte] = [Byte]()
p.append(15)
print(p[10])
So what is the mistake here? P.S. Thank you for your answer :)
You can't overload subscripts this way. Even if you could, you'd be creating an infinite loop in your implementation. Your implementation also would be illegal, since it returns something other than Element.
What you mean is something like this:
extension Array where Element: ByteProtocol {
subscript (wrapping index: Int) -> Element {
return self[Int(index % self.count)]
}
}
var p: [Byte] = [Byte]()
p.append(15)
print(p[wrapping: 10])
It doesn't give you an "overflow". It gives you an out-of-range error. There is no element index 10 in an array with only 1 element. The crash occurs before your subscript implementation is ever called (as you could easily discover by breakpointing it). You cannot magically change the meaning of an existing subscript implementation in the way you are hoping to.
The default implementation of subscript is called, not yours. Hence, it's trying to actually access the 10th element, which doesn't exist.
You can't override the behaviour of a struct like Array using an extension. They're not polymorphic. You can, however, add a new definition of a subscript, as rob showed.
I'm playing around with Swift trying to make it look more "dynamically typed" – just for fun, no production value expected.
Now I'm stuck with overwriting behavior of converting builtin types to String.
For example, I'd like to see this output for Array:
let nums = [1, 2, 3]
print(nums) // "I'm an array"
So far I tried to
make an extension to NSArray (not compiles)
implement CustomStringConvertible (not compiles)
make an extension to Array (compiles, changes nothing)
Seems like I'm on the wrong path:
extension Array: CustomStringConvertible {
public var description: String { return "An array" }
}
gives the warning:
Conformance of 'Array' to protocol 'CustomStringConvertible' was already stated in the type's module 'Swift'
Is this doable in Swift?
This does not work because Array overrides description. If array did not override it then it would print "An array". The class method 'wins' over the extension.
extension Array {
public var description: String { return "An array" }
}
You could create a Wrapper class for your array. It's a workaround but doesn't override array's description itself.
class ArrayWrapper<T> : CustomStringConvertible{
var array : Array<T> = Array<T>()
var description: String { return "An array" }
}
You could then use it like this.
var array = ArrayWrapper<Int>()
array.array = [1,2,3]
print(array) //prints "An Array"
print(array.array) //still prints "[1, 2, 3]"
Seems like it is not possible to neither subclass nor overwrite a builtin Array type.
We could use a wrapper though (credits to #Yannick) and implement a ArrayLiteralConvertible protocol, so we could use square brackets for initialization.
struct Array<T> {
let array: [T]
}
extension Array: ArrayLiteralConvertible {
init(arrayLiteral elements: T...) {
self.array = elements
}
}
extension Array: CustomStringConvertible {
var description: String { return "An array" }
}
let array = [1,2,3]
print(array) // "An array\n"
Swift does not allow you to provide a protocol override for a type that already declares that protocol:
Imagine that you have a set of integers and you want to override the default implementation of CustomStringConvertible for a Set
The simplest answer to all of this is: you can't, and that's a feature not a bug. Set isn't your type and you don't get to change its already-defined behavior like this.
The capability to intercept and alter the behavior of a type to do something different to what that type is originally designed to do is powerful, but fraught with downsides. In this case, you've chosen a fairly benign thing to alter, but in other cases doing this could do all sorts of damage to assumed invariants of a type by changing its program-wide behavior.
The best way to alter a type to do something different like this is to wrap it in your own low-cost struct. Unfortunately this does mean writing a fair amount of boilerplate for forwarding – though less and less these days as we gain features like synthesized conformances and dynamic member lookup. Hopefully someday we'll get features that do make easier to create a newtype with customized behavior.
https://forums.swift.org/t/whats-the-best-way-to-override-customstringconvertible-for-a-collection/24844
How would a simple function like index(of:) be implemented in a swift 3 extension. Please provide an example with the practices required to implement the example are.
If you are looking for the syntax of an extension of Array in Swift 3 here it is
extension Array {
func foo() { }
}
You can also restrict the extension to be available only where the Element of the array does conform to some protocol
extension Array where Element : Equatable {
func foo() { }
}
Trying to make and shuffle a card deck in Swift, made up of Card objects in an array called cardDeck. Then I want to shuffle it. Relevant code:
var cardDeck = [card]()
for ind in 1 ... 4
{
for ind2 in 1 ... 13
{
cardDeck.append(card(number: ind2, color: ind))
}
}
cardDeck = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(cardDeck)
"Cannot assign value of type [anyobject] to logic.card" ("logic" is the overall class name, and Card is another class within it)
Anyone know whats wrong? I guess the array is not an AnyObject type of array since I declared it as containing cards, right?
The problem is that arrayByShufflingObjectsInArray takes an [AnyObject] and returns an [AnyObject].
Therefore its going to completely throw away your [Card] type information that you provide as an input, and thus give you an error about not being able to convert types when you try to assign the output back to your original array. I suspect this is due to the method being implemented in Objective-C, not Swift.
A more Swifty version of the method would look like this:
func arrayByShufflingObjectsInArray<T:AnyObject>(array:[T]) -> [T] {
...
}
Through using generics, you can preserve the type information that you pass in to begin with, returning the same type that you inputted. In fact, you can write your own extension of GKRandomSource in order to do just that:
extension GKRandomSource {
func arrayOfSameTypeByShufflingObjectsInArray<T:AnyObject>(array:[T]) -> [T] {
return arrayByShufflingObjectsInArray(array) as! [T]
}
}
(Feel free to come up with a more catchy name for the method)
The force downcast is used as an ugly solution to the problem – which is why I recommend creating an extension, rather than using this directly. It cannot crash, as the output array is guaranteed to contain objects of the same type as the input (as the array you pass in can only contain a single type).
You can now use it like so:
cardDeck = GKRandomSource.sharedRandom().arrayOfSameTypeByShufflingObjectsInArray(cardDeck)