diff --git a/Guides/FirstNonNil.md b/Guides/FirstNonNil.md new file mode 100644 index 00000000..16e41a84 --- /dev/null +++ b/Guides/FirstNonNil.md @@ -0,0 +1,43 @@ +# First Non-Nil + +[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/FirstNonNil.swift) | + [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/FirstNonNilTests.swift)] + +Retrieves the first `.some` encountered while applying the given transform. + +This operation is available through the `firstNonNil(_:)` method on any sequence. + +```swift +let value = ["A", "B", "10"].firstNonNil { Int($0) } +// value == .some(10) +// +let noValue = ["A", "B", "C"].firstNonNil { Int($0) } +// noValue == .none +``` + + +This method is analogous to `first(where:)` in how it only consumes values until +a `.some` is found, unlike using lazy operators, which will load any sequence into a collection +before evaluating its transforms lazily. + +## Detailed Design + +The `firstNonNil(_:)` method is added as an extension method on the `Sequence` +protocol: + +```swift +public extension Sequence { + func firstNonNil(_ transform: (Element) throws -> Result?) + rethrows -> Result? +} + +``` + +### Naming + +This method’s name was selected for its comprehensibility. + +### Comparison with other languages + +**Scala**: Scala provides a `collectFirst` function that finds the first element +in a collection for which a partial function is defined. diff --git a/Sources/Algorithms/FirstNonNil.swift b/Sources/Algorithms/FirstNonNil.swift new file mode 100644 index 00000000..55258e67 --- /dev/null +++ b/Sources/Algorithms/FirstNonNil.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// firstNonNil(_:) +//===----------------------------------------------------------------------===// + +public extension Sequence { + /// Returns the first element in `self` that `transform` maps to a `.some`. + func firstNonNil( + _ transform: (Element) throws -> Result? + ) rethrows -> Result? { + for value in self { + if let value = try transform(value) { + return value + } + } + return nil + } +} diff --git a/Tests/SwiftAlgorithmsTests/FirstNonNilTests.swift b/Tests/SwiftAlgorithmsTests/FirstNonNilTests.swift new file mode 100644 index 00000000..bc07a39c --- /dev/null +++ b/Tests/SwiftAlgorithmsTests/FirstNonNilTests.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import Algorithms + +final class FirstNonNilTests: XCTestCase { + func testFirstNonNil() { + XCTAssertNil([].firstNonNil { $0 }) + XCTAssertNil(["A", "B", "C"].firstNonNil { Int($0) }) + XCTAssertNil(["A", "B", "C"].firstNonNil { _ in nil }) + XCTAssertEqual(["A", "B", "10"].firstNonNil { Int($0) }, 10) + XCTAssertEqual(["20", "B", "10"].firstNonNil { Int($0) }, 20) + } +}