SE-0032: Add `first(where:)` method to `Sequence`
- Proposal: SE-0032
- Author: Lily Ballard
- Review Manager: Chris Lattner
- Status: Implemented (Swift 3.0)
- Decision Notes: Rationale
- Bug: SR-1519
- Previous Revisions: 1
Introduction
Add a new extension method to Sequence called first(where:) that returns the found element.
Discussion on swift-evolution started with a proposal with title Add find method to SequenceType
Swift-evolution thread: Proposal: Add function SequenceType.find()
Motivation
It's often useful to find the first element of a sequence that passes some given predicate. For Collections you can call index(of:) or index(where:) and pass the resulting index back into the subscript, but this is a bit awkward. For Sequences, there's no easy way to do this besides a manual loop that doesn't require filtering the entire sequence and producing an array.
I have seen people write code like seq.lazy.filter(predicate).first, but this doesn't actually work lazily because .first is only a method on Collection, which means the call to filter() ends up resolving to the Sequence.filter() that returns an Array instead of to LazySequenceProtocol.filter() that returns a lazy sequence. Users typically aren't aware of this, which means they end up doing a lot more work than expected.
Proposed solution
Extend Sequence with a method called first(where:) that takes a predicate and returns an optional value of the first element that passes the predicate, if any.
Detailed design
Add the following extension to Sequence:
extension Sequence {
/// Returns the first element where `predicate` returns `true`, or `nil`
/// if such value is not found.
public func first(where predicate: @noescape (Self.Iterator.Element) throws -> Bool) rethrows -> Self.Iterator.Element? {
for elt in self {
if try predicate(elt) {
return elt
}
}
return nil
}
}Impact on existing code
None, this feature is purely additive.
In theory, we might provide an automatic conversion from seq.filter(predicate).first or seq.lazy.filter(predicate).first to seq.first(where: predicate), although the existing code would continue to compile just fine.
Alternatives considered
None