Swift Error with Generic Array

Swift error with generic array

Swift doesn’t have builtin behaviour to speculatively convert an array’s contents from one arbitrary type to another. It will only do this for two types that it knows have a subtype/supertype relationship:

class A { }
class B: A { }
let a: [A] = [B(),B()]
// this is allowed - B is a subtype of A
let b = a as? [B]

let a: [AnyObject] = [1,2,3]
// this is allowed - NSNumber is a subtype of AnyObject
let b = a as? [NSNumber]

struct S1 { }
struct S2 { }

let a = [S1(),S1()]
// no dice - S2 is not a subtype of S1
let b = a as? [S2]

The protocol doesn’t help either:

protocol P { }
struct S1: P { }
struct S2: P { }

let a = [S1(),S1()]
// still no good – just because S1 and S2 both conform to P
// doesn’t mean S2 is a subtype of S1
let b = a as? [S2]

Your example is basically a variant of this last one. You have an array of type [T], and you want to cast it to [MyType]. It’s important to understand that you do not have an array of type [MyProtocol]. Your generic type T is a specific type, which has to implement MyProtocol, but that’s not the same thing.

To see why you can’t just cast from any type to any other type, try this code:

protocol P { }
struct S: P { }

let a: [P] = [S(),S()]
let b = a as? [S]

This will generate a runtime error: "fatal error: can't unsafeBitCast between types of different sizes". This gives a hint as to why you can only cast an array from containing one reference type to a subtype – it’s because what is happening is just a bit cast from one pointer type to another. This will work for super/subtype classes, but not for arbitrary classes, structs, or protocols, as they have different binary representations.

Swift Generic Array 'not identical' error

Arrays in Swift are value types. That means that data is copied when passed into your exchange method, but you are trying to modify the copy to affect the original version. Instead you should do one of two things:

1. Define data as an inout parameter:

func exchange<T>(inout data:[T], i:Int, j:Int)

Then when calling it you have to add an & before the call:

var myArray = ["first", "second"]
exchange(&myArray, 0, 1)

2. Return a copy of the Array (recommended)

func exchange<T>(data:[T], i:Int, j:Int) -> [T]
{
var newData = data
newData[i] = data[j]
newData[j] = data[i]
return newData
}

I recommend this way over the in-out parameter because in-out parameters create more complicated state. You have two variables pointing to and potentially manipulating the same piece of memory. What if exchange decided to do its work on a separate thread? There is also a reason that Apple decided to make arrays value types, using in-out subverts that. Finally, returning a copy is much closer to Functional Programming which is a promising direction that Swift can move. The less state we have in our apps, the fewer bugs we will create (in general).

Extending array for object with generic type gives error

What you need is to move the constrain from the extension to the method, create a generic Equatable type and add a where clause constraining the collection Element to Foo

extension Collection {
func updateElement<T: Equatable>(element: Element) where Element == Foo<T> {

}
}

Swift function taking generic array

To start, you're using the ternary operator (?:) in quite a complex situation. If you first modify your code to use an if statement instead, the compiler error that was appearing in the call to outputArray.append(data) - Generic parameter 'Element' could not be inferred - appears in a much more sensible place (namely, the line of the if statement).

Generic parameter inference fails

That error is relatively easy to solve - simply replace Array with Array<Any>, giving us this:

func flatten(input: [Any]) -> [Any] {
var outputArray = [Any]()

for i in 0..<input.count {
let data = input[i]
if data is Array<Any> {
outputArray += flatten(input: [data])
} else {
outputArray.append(data)
}
}

return outputArray
}

At this point, the original problem still occurs, because the value that's passed in to flatten(input:) is [data]. We know, due to the fact that the if condition was true, that data is really of type Array<Any>, and so the value [data] must be an Array<Array<Any>>. Instead, we want to pass in data, which is already an array.

You say that the reason you wrote [data] is because the argument has to be an array, and so you were "forced to" by the compiler. In fact, the only thing the compiler is forcing you to do is pass in an argument whose type is declared as Array<Any>. We've made sure that data is an array using the if statement, but data is still declared as an Any (because it was an element of input, which is an Array<Any>), so the compiler has no idea that it's really an array.

The solution is - instead of using if data is Array<Any> to determine momentarily whether data is an array but immediately throw that information away - to convert data to an Array<Any>.

The new if statement becomes if let dataArray = data as? Array<Any>. The statement data as? Array<Any> attempts to convert data to an array, returning a value of type Array<Any> if successful or nil otherwise. Then the if let dataArray = ... statement stores the value in dataArray and returns true if given a non-nil value, or returns false if given a nil value (this is called conditional binding).

By doing that, in the true case of the if statement we have access to a value dataArray that is of type Array<Any> - unlike data, which is only declared as Any. Then dataArray can be passed in to flatten(input:), and won't be nested inside another Array.

func flatten(input: [Any]) -> [Any] {
var outputArray = [Any]()

for i in 0..<input.count {
let data = input[i]
if let dataArray = data as? Array<Any> {
outputArray += flatten(input: dataArray)
} else {
outputArray.append(data)
}
}

return outputArray
}

A couple of other notes:

Array<Any> is of course equivalent to [Any], so the if statement could be written with that (generally preferred) syntax, like so:

if let dataArray = data as? [Any] {
outputArray += flatten(input: dataArray)
}

Also, there's no need to go through the whole for i in 0..<input.count { let data = input[i] ... ordeal if you just iterate over the array instead, like so:

func flatten(input: [Any]) -> [Any] {
var outputArray = [Any]()

for data in input {
if let dataArray = data as? [Any] {
outputArray += flatten(input: dataArray)
} else {
outputArray.append(data)
}
}

return outputArray
}

Generic Extension to Array Not Working

Give this a shot

extension Array where Element: Equatable {
func replaced (each valueToReplace: Element, with newValue: Element) -> [Element] {
var newArray = [Element]()
newArray.reserveCapacity(self.count)

for element in self {
let newElement = (element == valueToReplace) ? newValue : element
newArray.append(newElement)
}

return newArray
}

mutating func replace(each valueToReplace: Element, with newValue: Element) {
for (i, element) in self.enumerated() {
if element == valueToReplace { self[i] = newValue }
}
}
}

var j = [1,2,3,4,3,6,3,8,9]
var newArray = j.replaced(each: 3, with: 0)

It would be better to remove the redundancy by just making replaced delegate to replace:

extension Array where Element: Equatable {
func replaced(each valueToReplace: Element, with newValue: Element) -> [Element] {
var copy = self
copy.replace(each: valueToReplace, with: newValue)
return copy
}

mutating func replace(each valueToReplace: Element, with newValue: Element) {
for (i, element) in self.enumerated() {
if element == valueToReplace { self[i] = newValue }
}
}
}


Related Topics



Leave a reply



Submit