get count of generic array

Hello,


I'am hitting the limits of generics again...

Suppose I have an optional variable that is either nil or contains an array of some type.

I need the count of the array.

If x wasn't optional I could use AnySequence to erase the associated type:


var x:Any?
let y = [1,2]
x = y
var count:Int
count = AnySequence(y).underestimateCount() // 2
if let z = x as? [Any] {count = AnySequence(z).underestimateCount()}
else { print("No luck") } // "No luck"


Anyone?

I’m not sure I understand your example. If

x
is “either nil or contains an array of some type”, why declare it as
Any?
? Why not declare it as
[Any]?
. Additionally, you said that you’ve hit the “limits of generics” but
Any
is not generic.

I think I’m going to need more context here before I can understand where you’re going with this.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I wanted to keep the example simple.

Actually x can be nil, a single instance or an array.

"Any" may not be generic but Array is.


I just would like to be able to do


if let z = x as? [Any] {{count = z.count}


rather than


if let z = x as [Type1] {count = z.count}
else if let z = x as [Type2] {count = z.count}
.....


I was secretly hoping somebody would have a trick for this because I hate having to write these switches on type all the time.

I think there is something else going on here that makes your goal impossible, at least via this route. I note that, in a playground, adding this line to your initial code:


if y is [Any] { print ("OK") }


produces this error message:


error: 'Any' (aka 'protocol<>') is not a subtype of 'Int'


Based on a discussion in another thread, I believe the problem is that an array of elements conforming to a protocol has a different internal structure to an array of structs or class references. [SomeProtocol] is actually [InternalStructDescribingProtocol], where the elements are an internal struct that describes what the actual type of the value is, plus the value itself, or a pointer to an instance.


For this reason, you can't get from [SomeProtocol] to [SomeStruct] with the 'as' operator, since the operator signals a reinterpretation of type, not conversion of value. Putting this another way, different arrays may have different element sizes, even when the elements conform to the same protocol. What you're really want, as you hinted in the title of the thread, is [T] where T is a generic type, and that construct doesn't exist in Swift in contexts like this.


(The waters are muddied here because Swift can also do bridging between Array<> and NSArray, where it conceptually converts the underlying data rather than just casting the type. But apart from bridging special cases, casting — the 'as' operator — does not do conversion.)


What I would suggest:


1. Since you're really trying to bypass Swift's type checking by using (informally-described) "generic" arrays, use class NSArray explicitly. That is, implement your logic as if you're in the Obj-C conceptual world.


2. Use an enum whose cases have associated values to represent the value stored in 'x'. You could have 3 cases: None, Single, Array. However, unless your arrays are actually [Any] at their point of definition, you'll still run into a problem trying to assign between [Any] and [Int] — it's still not generic in the way you want.


Disclaimer: I'm not an expert on this, so I may be mis-describing what Swift expects. What I've said represents my best understanding, so far.

I agree with QuinceyMorris about why this is failing. Swift won’t coerce (using any of the ‘as’ operators) between

Array<SomeType>
and
Array<SomeProtocol>
, where
SomeType
implements
SomeProtocol
, because such conversions require a change of representation and thus are expensive. For example:
protocol SomeProtocol {
}

struct SomeType : SomeProtocol {
}

let oneType = SomeType()
let oneProtocol = oneType as SomeProtocol

let arrayOfType = [SomeType(), SomeType()]
let arrayOfProtocol = arrayOfType as [SomeProtocol]
// error: cannot convert value of type '[SomeType]' to type '[SomeProtocol]' in coercion
Any
is a protocol, which is why your first snippets fails.

Actually x can be nil, a single instance or an array.

If I were in your shoes I’d take a step back and try to avoid getting into this state in the first place. When I work in Swift I try to avoid

Any
and
AnyObject
as much as possible, partly because of problems like this, but mostly because it undermines Swift’s type checker. In your case I suspect you’d be better served by making the calling function generic based on the elements in the array.

Alternatively, you could go fully dynamic, using

NSArray
and
AnyObject
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

@eskimo, QuinceyMorris


Thank you for pointing out the problems with this approach.

I did very little Obj-C programming, going straight from C to Swift and I would like to write Swift-only code as much as possible.


One solution might be to make all single instances in x arrays containing one element. Then I could type x as [Any]?

Another would be to have two variables, x1:Any? and x2:[Any]? and put the arrays in x2.

Both approaches would make my code less readable and more prone to error.

So I’ll have to live with the switches on type.

get count of generic array
 
 
Q