Cannot iterate over a generic collection

I don't know why I'm getting the following error as I'm trying to iterate over a collection that is generic:

struct Trie<T: Hashable> {
    private var root = Node<T>()
    
    mutating func addChild(of content: any Collection<T>) {
        for token in content { // this line spits out the following error: Type 'any Collection<T>' cannot conform to 'Sequence'
            
        }
    }

PS Feel free to correct anything else that you see not being Swift idiomatic code

You probably want some Collection<T> here which would solve your error, though I don’t see any reason why this shouldn’t work in theory—it’s just not currently supported and would need an evolution proposal.

2 Likes

You need to be using Swift 5.7.

OMG it worked! Wait, I don't get it! I thought some has to to with opaque types, where the code that is being called decides the concrete return type, while any simply means you can pass any concrete type that conforms to the protocol.
Am I missing something?

SE-0341 expanded opaque types to parameter positions, where they serve as shorthand for a generic type parameter. In parameter position, some Collection and any Collection are nearly identical, with a few exceptions. One of those, which you’ve stumbled across, is that some Collection conforms to collection but any Collection doesn’t.

1 Like

Updated: I wasn't aware of how Swift 5.7 simplifies the expression of constraints so I want to update this to not confuse readers that don't read the following responses. Thanks to @Jumhyn & @jayton :smiley:

For Swift 5.6 your mutating function could potentially be written like this:

mutating func addChild<U: Collection>(of content: U) {
    for token in content { // this is fine *sips coffee*
        // do whatever with this the elements
    }
}

And if you want to make sure the elements in the collection are of the type T in your generic struct:

mutating func addChild<U: Collection>(of content: U) where U.Element == T {
    for token in content { // now token is of type T
        token.someFunctionOnT()
    }
}

Depending in what T is that equality constraint might also be just a conformance to a T-related protocol or something, I assume that's obvious.

Again, you might be totally okay with using some, but in my experience chances are high you're actually more interested in a generic function instead of a function with "just" kind-of-generic parameters. Oh, and since your Trie type is already generic over some type T you might also want to use a different character to denote the generic type in the function, at least for readability (and depending on whether you mean this to be the same type or not). If the T from the Trie and the T in the function have a relationship you might also define some constraint here (and then you need to use different labels). (left this as strike-through so @Jumhyn's and @jayton's answers don't look out of context.)

1 Like

The version of this function which accepts some Collection<T> is simply shorthand for the fully-spelled-out generic version—there’s no “kind of” about it.

In a situation where you need an explicit name for the generic parameter (e.g., you’re constraining more than just one of the primary associated types, or you need to use the same generic parameter for more than one parameter or something) you might have to fall back to the long-hand version for full flexibility, but in the simple cases there’s no difference between the two spellings in terms of behavior/semantics.

2 Likes

Ah, thank you, I didn't know that yet. I wasn't able to read through the proposal or play around with Swift 5.7 so far. :)
I guess when using a collection you most likely want to constrain the element a bit, so I'll leave my answer above unedited. Of course you can still do a type check with as? inside the loop, alternatively, but if you already know you'll use it on something specific further constraints are at least an option.

1 Like

In Swift 5.7, you can also write mutating func addChild(of content: some Collection<String>).

2 Likes

Ah! Now it clicked, that also makes a lot more sense for @danmihai's initial snippet! I updated my previous reply (sheepishly...)

1 Like

This is true, but Swift 5.7 also brought the any Collection<Element> syntax as well. That's part of what allows the original code to compile. So I'm very confused how the conversation continued as it did. :thinking:

The only issue is that 5.7 also made true and false the same thing, causing all of our organs to collapse upon themselves in a very painful way, after which, extinct nanobots from dimension zeta cleaned up any remaining anomalies that would have left any trace of our universe's history, including the new generic features.


Aside from that, I'm very happy with the usability improvements. Worth it. :trophy:

3 Likes