func ??<S>(lhs: S?, rhs: S) -> S where S : Sequence, S: ExpressibleByArrayLiteral {
switch lhs {
case .some(let s):
return s
case .none:
return rhs
}
}
In order to make sure existing sequences that are expressible by [] aren't accidentally wrapped in an AnySequence.)
I did a quick check of the compatibility suite. It was a simple regex + eyeball so I think it avoids false positives but will probably be off +- a few.
I could see 20 examples of for with ?? [] and 55 examples of if let immediately followed by use in a for. Overall 24 projects used one of these patterns, so just over a quarter of the projects.
edit: adding in ?.forEach, of which there are 15 occurrences.
Anecdotally, this is a pretty high occurrence rate based on searches for similar patterns for other reviews I've done in the past.
I tried to spot and exclude those. Also there were quite a few cases to exclude where the let was used as an argument to the sequence-producing function rather than the iteree itself, which I also excluded, though strictly speaking, if we had an optional for syntax, that would allow map to be used in those cases.
In our codebase (about 100k lines of Swift mixed in with about 1M lines of ObjC) there are 13 matches for @algal's for _ in _ ?? [] regex. 6 of them involve optional chaining, and all of those are related to an optional NSView/UIView (e.g. for subview in superview?.subviews ?? []).
Our in-house Swift style is generally to guard exclude optionals as soon as is practical and avoid dealing with them. I even notice that there are at least 5 places where it might have been natural to use optional chaining with sequences (outside of a for) and I see constructions like (getOptionalSequence() ?? []).map({...}).filter({...}) instead.
Our codebase has ~70,000 lines of swift code, we match 11 times for the regex looking for if let statements followed by for loops. 2 is the result of poor representation of data, resulting in conditional casting, the rest could have used ?.forEach or ?? [] (though using the ?.forEach on some of those would involve unfortunate parenthesis).
I have 56 instance of for x in sequenceExpression ?? [] in my code base, roughly 10% of all the for loops. Half of those come from XML parsing code (where you must always worry about parts of the structure being missing). Things like this:
for noteElement in unitElement.first("notes")?.all("note") ?? [] {
// do something with the note
}
Currently the filters for XML elements of interest all return eagerly constructed arrays, so using sequenceExpression ?? [] works nicely. It'd be better to return lazy sequences, but then all the loops would need to be wrapped like this:
if let notesElement = unitElement.first("notes") {
for noteElement in notesElement.all("note") {
// do something with the note
}
}
Also, trying to mix ?? [] with lazy sequences could trigger the the trap unearthed by @Ben_Cohen, which could make the algorithm eager again without notice. That's a concern, although it's not anything specific to for loops.
In a 500k+ line codebase I have access to I see 210 results for this regex. A lot of this code was written by people not familiar with Swift idioms so probably half of those fall into the stored optional array anti-pattern, however many of them are the result of optional chains and other reasonable code patterns.
Yeah, this was one of the possibilites that came up, which I listed in my summary. I added a link to the summary into the original post to hopefully make this more discoverable.
The pattern .+ in .+ \?\? \[\] produces only 1 true positive:
for e in n.geometry?.elements ?? [] { … }
and many false positives such as:
for set in a1 { x4.formUnion(set ?? []) }
and
let objs: [TagItem] = selectedTagName.map { selTagName in document.catalog.tagItems.filter { $0.name == selTagName } } ?? []
in our 200k line codebase.
And the pattern if[[:space:]]let[[:space:]]*\w+[[:space:]]*=[[:space:]]*.*[[:space:]]*\{[[:space:]]*for produces zero matches. I had to double check by removing the last "for" which resulted in a lot of matches so for some reason our code base doesn't have a single occurrence of if let a = b { for.
I only see one returning Array. I mean if Sequence.reversed() returns the same Sequence type(Self)—which feels very natural—the problematic code won't compile at all. There must be good reasons that the current API returns Array instead of Self(same for sorted()), what are they?
To reverse something that doesn't provide iteration from the last to first element (not just sequences, but forward-only collections as well), you must stream all the elements into some kind of storage first, then return them in reverse order. If you're going to do that, it may as well be an array because having an array is a useful thing to have. You could instead return some kind of opaque reversed thing, but that wouldn't help – you still have to allocate memory for all those elements.
Re your discussion with @Ling_Wang
That trap seems very specific to the combination of .? and ?? []. Neither of the following IMO would result in this:
Rust's unwrap_or
Java's orElse
Scala's getOrElse
...and I certainly can't easily recreate that with a similar extension method.