Michael Ozeryansky
Michael Ozeryansky

Reputation: 8053

How to return an empty SubSequence in a Collection extension

I am creating an extension to Collection which returns a SubSequence. However, if the function's condition isn't valid I want to return an empty SubSequence instead of nil.

How can I return an empty SubSequence in an extension to Collection?

// basic example:
extension Collection {

   func myFunction(condition: Bool) -> SubSequence {
      guard condition else {
          return *EmptySubSequence*
      }
      ...
   }

}

This would be very easy if I just did an extension on a concrete type, but before I limit my extension to a specific type, I would like to know if it can apply to Collection itself.

The best I've found is prefix(0) will return a SubSequence of length 0.

Upvotes: 2

Views: 337

Answers (2)

Rob Napier
Rob Napier

Reputation: 299505

While I believe Leo's answer is very helpful, I wouldn't use it in this case. There is no need to add an additional requirement such as RangeReplaceableCollection, which also adds a replaceSubrange requirement. Adding these requirements limits the extension more than needed. For example, Range is not a RangeReplaceableCollection. (If you really only plan to ever call this on Array, then I recommend making that explicit and forget about Collection.)

Creating a "fresh" subsequence means that it becomes unrelated to the original collection, and that breaks the meaning of the indices. Calling init is going create something with a "zero-like" start index that may not be compatible with the collection you're working on. You can't safely compare indices from different collections, even of the same type.

Sweeper's recommendation of prefix(0) is better, but I think anchoring to the beginning isn't helpful for common algorithms that would need this.

Instead, I recommend a subsequence at the end of the collection: self[endIndex...].

Consider an algorithm for myFunction that processes some portion of the collection, possibly skipping over some part, and returning the first "important" part (i.e. a first stage of a tokenizer that skips over whitespace). The endIndex would naturally tell you the last place it looked, and allow you to start the next search at that point. Returning an empty subsequence from this collection means that the indices are always compatible. You can always safely set startSearchingFrom = result.endIndex, even in the case that it's empty.

In most cases, getting the indices right won't matter. But in most cases, you probably just wanted to use this on Array anyway and it didn't need to be generic. If you are going to make it generic, IMO you should make sure to keep all the semantics of the types you're working on, and that means getting the indices right.

Upvotes: 1

Leo Dabus
Leo Dabus

Reputation: 236458

If you need to initialize an empty collection you should extend RangeReplaceableCollection instead of Collection. RangeReplaceableCollection requires the types which conform to it to implement an empty initializer.


extension RangeReplaceableCollection {
    func myFunction(condition: Bool) -> SubSequence {
        guard condition else {
            return .init()
        }
        // ...
    }
}

Upvotes: 4

Related Questions