Compiler can't infer that flat-mapped NonEmpty<[String]> results in [String]
wearhere opened this issue · comments
struct Emoji {
let aliases: NonEmpty<[String]>
let tags: [String]
}
let emoji = /* an Emoji */
let searchTerms: [String] = [emoji.aliases, emoji.tags].flatMap { $0 } // error
The error is Cannot convert value of type '[Any]' to specified type '[String]'
.
I'm using Xcode 11.4.1 and Swift 5.
your [emoji.aliases, emoji.tags]
is of type [Any]
and you've specified the searchTerms
to be [String]
@ibrahimkteish I don't understand your implication, can you clarify? Regardless of the type of [emoji.aliases, emoji.tags]
, it remains that the code above works if aliases
is typed as [String]
and not when it is typed as NonEmpty<[String]>
. As far as I understand the library, NonEmpty
is supposed to present as an instance of whatever collection it wraps in every way except for enforcing non-emptiness, so this seems like a bug.
The problem here is that Swift does not yet have disjoint types (like Either
).
emoji.aliases
is a NonEmpty
, and emoji.tags
is an Array
, so [emoji.aliases, emoji.tags]
is a[Any]
(AKA Array<Any>
) (ideally, it might be a [(NonEmpty<[String]>|[String])]
, but again, Swift can't do that yet. It'd also be nice if it were a [Collection]
, but knowing that the sub-collections all have String
Element
s is also not possible yet in Swift).
Because that array literal is a [Any]
, it doesn't have a flatMap
method; it doesn't see that its contents are all collections which can be flattened.
All you have to do is prove to the compiler that these are compatible collections - currently the only way to do that is by making them the same type (turn the NonEmpty<[String]>
into a [String]
or vice versa).
Here's a version of your code that works:
import NonEmpty
struct Emoji {
let aliases: NonEmpty<[String]>
let tags: [String]
}
let emoji = Emoji(aliases: NonEmpty("smile", "grin"), tags: ["smiley", "face"])
let searchTerms: [String] = [Array(emoji.aliases), emoji.tags].flatMap { $0 }
Thanks for the explanation @BenLeggiero ! Turning the NonEmpty<[String]>
into a [String]
like that doesn’t look too bad. I don't know if you'd prefer to keep this issue open to track adopting the (eventual) Swift features you mention, but if you'd like to close that's fine by me.
Glad I could help, @wearhere! I think it's probably best to close this since we don't know for sure whether Swift will ever support disjoint type combinations
I'm tagging @stephencelis and @mbrandonw since they control this repo
Glad y'all figured this out, thanks for the help @BenLeggiero and @ibrahimkteish!
Another way of doing this would be to wrap the collections in AnyCollection
in order to get Swift to think it's a homogenous collection:
[AnyCollection(emoji.aliases), AnyCollection(emoji.tags)]
.flatMap { $0 }
It's a bit longer, but has the potential benefit that it won't allocate an array for emoji.aliases
, it will just wrap the collection.