New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stdlib] SR-1519: Implement SE-0032: Add Sequence.first(predicate:) #2529

Merged
merged 5 commits into from May 16, 2016

Conversation

Projects
None yet
2 participants
@russbishop
Contributor

russbishop commented May 14, 2016

What's in this pull request?

Implementation of SE-0032, adds first() to Sequence protocol along with default implementation.

I modified FindTest to give each of the elements a separate identity so the find test can verify it found the first element and not just any matching element.

Resolved bug number: (SR-1519)


Before merging this pull request to apple/swift repository:

  • Test pull request on Swift continuous integration.

Triggering Swift CI

The swift-ci is triggered by writing a comment on this PR addressed to the GitHub user @swift-ci. Different tests will run depending on the specific comment that you use. The currently available comments are:

Smoke Testing

Platform Comment
All supported platforms @swift-ci Please smoke test
OS X platform @swift-ci Please smoke test OS X platform
Linux platform @swift-ci Please smoke test Linux platform

Validation Testing

Platform Comment
All supported platforms @swift-ci Please test
OS X platform @swift-ci Please test OS X platform
Linux platform @swift-ci Please test Linux platform

Note: Only members of the Apple organization can trigger swift-ci.

Show outdated Hide outdated stdlib/public/core/Sequence.swift
@@ -950,13 +962,32 @@ extension Sequence {
///
/// - Parameter body: A closure that takes an element of the sequence as a
/// parameter.
@warn_unused_result

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

Looks like an unrelated edit... Even more, @warn_unused_result does nothing now.

@gribozavr

gribozavr May 14, 2016

Collaborator

Looks like an unrelated edit... Even more, @warn_unused_result does nothing now.

This comment has been minimized.

@russbishop

russbishop May 14, 2016

Contributor

Sorry, my fault. I also didn't realize @discardable_result landed already.

@russbishop

russbishop May 14, 2016

Contributor

Sorry, my fault. I also didn't realize @discardable_result landed already.

Show outdated Hide outdated stdlib/public/core/Sequence.swift
@warn_unused_result
func first(
predicate: @noescape (Iterator.Element) throws -> Bool
) rethrows -> Iterator.Element?

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

For this part, we need a "dispatch" test in validation-test/stdlib/SequenceType.swift.gyb to ensure that we have a requirement.

@gribozavr

gribozavr May 14, 2016

Collaborator

For this part, we need a "dispatch" test in validation-test/stdlib/SequenceType.swift.gyb to ensure that we have a requirement.

This comment has been minimized.

@russbishop

russbishop May 14, 2016

Contributor

Ahhh I looked at this again and I think I see what the dispatch test is doing. I'll fix it.

@russbishop

russbishop May 14, 2016

Contributor

Ahhh I looked at this again and I think I see what the dispatch test is doing. I'll fix it.

Show outdated Hide outdated stdlib/private/StdlibCollectionUnittest/CheckSequenceType.swift
self.sequence = sequence.map(MinimalEquatableValue.init)
var elementIndex = 0
self.sequence = sequence.map {
defer { elementIndex += 1 }

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

It is strongly discouraged to write side-effectful closures for map(). I'd recommend sequence.enumerated().map { ... }.

@gribozavr

gribozavr May 14, 2016

Collaborator

It is strongly discouraged to write side-effectful closures for map(). I'd recommend sequence.enumerated().map { ... }.

This comment has been minimized.

@russbishop

russbishop May 14, 2016

Contributor

No problem, I'll change it.

@russbishop

russbishop May 14, 2016

Contributor

No problem, I'll change it.

Show outdated Hide outdated stdlib/public/core/Sequence.swift
public func first(
predicate: @noescape (Iterator.Element) throws -> Bool
) rethrows -> Iterator.Element? {
for element in self {

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

Base it on forEach instead? forEach might be cheaper (by a constant factor) for some kinds of collections with complex iteration patterns, for example, trees.

@gribozavr

gribozavr May 14, 2016

Collaborator

Base it on forEach instead? forEach might be cheaper (by a constant factor) for some kinds of collections with complex iteration patterns, for example, trees.

This comment has been minimized.

@russbishop

russbishop May 14, 2016

Contributor

forEach can't break out of iterating the entire sequence which IMHO would be surprising behavior, especially for non-restartable or non-terminating sequences.

(I actually think the forEach API could use a termination flag for early exit but that's another discussion.)

@russbishop

russbishop May 14, 2016

Contributor

forEach can't break out of iterating the entire sequence which IMHO would be surprising behavior, especially for non-restartable or non-terminating sequences.

(I actually think the forEach API could use a termination flag for early exit but that's another discussion.)

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

forEach can't break out of iterating the entire sequence

We can throw from the closure passed to forEach :) I'm not sure if the semantic analysis will accept that and not confuse it with the rethrow semantics, but it is worth a shot.

@gribozavr

gribozavr May 14, 2016

Collaborator

forEach can't break out of iterating the entire sequence

We can throw from the closure passed to forEach :) I'm not sure if the semantic analysis will accept that and not confuse it with the rethrow semantics, but it is worth a shot.

This comment has been minimized.

@russbishop

russbishop May 14, 2016

Contributor

This didn't work at some point in the past but rethrow has gotten smarter since then. I tested that it doesn't interfere with throwing custom errors, nor does it break rethrows for a non-throwing closure.

I wanted to get your opinion on this before I push a commit since it looks "interesting" at first glance 😁

enum _ForEachStopIteration: ErrorProtocol {
    case stopIteration
}

extension Sequence {
    /// Calls the given closure on each element in the sequence in the same order
    /// as a `for`-`in` loop.
    ///
    /// - Parameter body: A closure that takes an element of the sequence as a
    ///   parameter. Return `true` from this closure to stop iteration.
    ///   Returning `false` will continue iteration.
    func _forEach(_ body: @noescape (Iterator.Element) throws -> Bool) rethrows {
        do {
            try self.forEach {
                if try body($0) {
                    throw _ForEachStopIteration.stopIteration
                }
            }
        } catch is _ForEachStopIteration { }
    }

    /// <insert find description here>
    public func find(where predicate: (Iterator.Element) throws -> Bool) rethrows -> Iterator.Element? {
        var foundElement: Iterator.Element? = nil
        try self._forEach {
            if try predicate($0) {
                foundElement = $0
                return true
            }
            return false
        }
        return foundElement
    }
}
@russbishop

russbishop May 14, 2016

Contributor

This didn't work at some point in the past but rethrow has gotten smarter since then. I tested that it doesn't interfere with throwing custom errors, nor does it break rethrows for a non-throwing closure.

I wanted to get your opinion on this before I push a commit since it looks "interesting" at first glance 😁

enum _ForEachStopIteration: ErrorProtocol {
    case stopIteration
}

extension Sequence {
    /// Calls the given closure on each element in the sequence in the same order
    /// as a `for`-`in` loop.
    ///
    /// - Parameter body: A closure that takes an element of the sequence as a
    ///   parameter. Return `true` from this closure to stop iteration.
    ///   Returning `false` will continue iteration.
    func _forEach(_ body: @noescape (Iterator.Element) throws -> Bool) rethrows {
        do {
            try self.forEach {
                if try body($0) {
                    throw _ForEachStopIteration.stopIteration
                }
            }
        } catch is _ForEachStopIteration { }
    }

    /// <insert find description here>
    public func find(where predicate: (Iterator.Element) throws -> Bool) rethrows -> Iterator.Element? {
        var foundElement: Iterator.Element? = nil
        try self._forEach {
            if try predicate($0) {
                foundElement = $0
                return true
            }
            return false
        }
        return foundElement
    }
}

This comment has been minimized.

@gribozavr

gribozavr May 15, 2016

Collaborator

This LGTM, except I'd probably prefer to inline _forEach into its only use site. return true and return false are less readable than just throwing and catching, I think.

@gribozavr

gribozavr May 15, 2016

Collaborator

This LGTM, except I'd probably prefer to inline _forEach into its only use site. return true and return false are less readable than just throwing and catching, I think.

This comment has been minimized.

@russbishop

russbishop May 15, 2016

Contributor

That's fine with me. I was considering putting in a proposal for a public version of forEach that allows stopping but I don't care for returning a Bool from the closure and an inout parameter seemed strange so maybe it just doesn't fit as a stdlib API.

@russbishop

russbishop May 15, 2016

Contributor

That's fine with me. I was considering putting in a proposal for a public version of forEach that allows stopping but I don't care for returning a Bool from the closure and an inout parameter seemed strange so maybe it just doesn't fit as a stdlib API.

Show outdated Hide outdated validation-test/stdlib/SequenceType.swift.gyb
// first()
//===----------------------------------------------------------------------===//
SequenceTypeTests.test("first/Predicate") {

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

Please add this test to stdlib/private/StdlibCollectionUnittest/CheckSequenceType.swift instead, since first(where:) is a customization point and different sequences and collections can implement it differently.

@gribozavr

gribozavr May 14, 2016

Collaborator

Please add this test to stdlib/private/StdlibCollectionUnittest/CheckSequenceType.swift instead, since first(where:) is a customization point and different sequences and collections can implement it differently.

This comment has been minimized.

@russbishop

russbishop May 14, 2016

Contributor

I'll move it. It still isn't quite clear to me exactly where the different kinds of tests belong. Is that stuff documented anywhere?

@russbishop

russbishop May 14, 2016

Contributor

I'll move it. It still isn't quite clear to me exactly where the different kinds of tests belong. Is that stuff documented anywhere?

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

Sorry, it is not documented. The general principle is that we want to put all algorithm tests into the reusable StdlibCollectionUnittest module, so that we can maximize our algorithm and collection coverage.

@gribozavr

gribozavr May 14, 2016

Collaborator

Sorry, it is not documented. The general principle is that we want to put all algorithm tests into the reusable StdlibCollectionUnittest module, so that we can maximize our algorithm and collection coverage.

Show outdated Hide outdated stdlib/public/core/Sequence.swift
/// - Returns: The first match or `nil` if there was no match.
@warn_unused_result
func first(
predicate: @noescape (Iterator.Element) throws -> Bool

This comment has been minimized.

@gribozavr

gribozavr May 14, 2016

Collaborator

The signature is first(where:) according to the proposal.

@gribozavr

gribozavr May 14, 2016

Collaborator

The signature is first(where:) according to the proposal.

This comment has been minimized.

@russbishop

russbishop May 14, 2016

Contributor

Also my fault for not realizing the ability to use keywords as parameters change already landed.

@russbishop

russbishop May 14, 2016

Contributor

Also my fault for not realizing the ability to use keywords as parameters change already landed.

@russbishop

This comment has been minimized.

Show comment
Hide comment
@russbishop

russbishop May 15, 2016

Contributor

@gribozavr Pushed the other changes, all tests pass locally. Just waiting to get your opinion on the throw to stop iteration code.

Contributor

russbishop commented May 15, 2016

@gribozavr Pushed the other changes, all tests pass locally. Just waiting to get your opinion on the throw to stop iteration code.

Show outdated Hide outdated validation-test/stdlib/SequenceType.swift.gyb
SequenceTypeTests.test("first/dispatch") {
let tester = SequenceLog.dispatchTester([OpaqueValue(1)])
tester.first { return $0.value == 1 }

This comment has been minimized.

@gribozavr

gribozavr May 15, 2016

Collaborator

return is probably not needed here.

@gribozavr

gribozavr May 15, 2016

Collaborator

return is probably not needed here.

@russbishop

This comment has been minimized.

Show comment
Hide comment
@russbishop

russbishop May 15, 2016

Contributor

@gribozavr I think this is ready to go unless you can think of anything else

Contributor

russbishop commented May 15, 2016

@gribozavr I think this is ready to go unless you can think of anything else

Show outdated Hide outdated stdlib/public/core/Sequence.swift
@@ -959,6 +970,34 @@ extension Sequence {
}
}
private enum _StopIteration: ErrorProtocol {

This comment has been minimized.

@gribozavr

gribozavr May 15, 2016

Collaborator

Sorry, one more thing -- we can't use private in the library due to a compiler bug. Please use internal.

Please add a space before the colon to conform to the library coding style.

@gribozavr

gribozavr May 15, 2016

Collaborator

Sorry, one more thing -- we can't use private in the library due to a compiler bug. Please use internal.

Please add a space before the colon to conform to the library coding style.

Show outdated Hide outdated stdlib/public/core/Sequence.swift
@@ -959,6 +970,34 @@ extension Sequence {
}
}
private enum _StopIteration: ErrorProtocol {
case stop

This comment has been minimized.

@gribozavr

gribozavr May 15, 2016

Collaborator

Indentation is two spaces.

@gribozavr

gribozavr May 15, 2016

Collaborator

Indentation is two spaces.

This comment has been minimized.

@russbishop

russbishop May 15, 2016

Contributor

Having separate tab guidelines between projects is killer :D

@russbishop

russbishop May 15, 2016

Contributor

Having separate tab guidelines between projects is killer :D

@gribozavr

This comment has been minimized.

Show comment
Hide comment
@gribozavr

gribozavr May 15, 2016

Collaborator

@russbishop Thank you, LGTM modulo two style comments!

Collaborator

gribozavr commented May 15, 2016

@russbishop Thank you, LGTM modulo two style comments!

@russbishop

This comment has been minimized.

Show comment
Hide comment
@russbishop

russbishop May 15, 2016

Contributor

@gribozavr Done!

Contributor

russbishop commented May 15, 2016

@gribozavr Done!

@gribozavr

This comment has been minimized.

Show comment
Hide comment
@gribozavr

gribozavr May 15, 2016

Collaborator

@swift-ci Please test and merge

Collaborator

gribozavr commented May 15, 2016

@swift-ci Please test and merge

@gribozavr

This comment has been minimized.

Show comment
Hide comment
@gribozavr

gribozavr May 15, 2016

Collaborator

@russbishop Could you take a look at the build errors?

Collaborator

gribozavr commented May 15, 2016

@russbishop Could you take a look at the build errors?

@russbishop

This comment has been minimized.

Show comment
Hide comment
@russbishop

russbishop May 15, 2016

Contributor

@gribozavr Sorry about that, pushed a fix.

Contributor

russbishop commented May 15, 2016

@gribozavr Sorry about that, pushed a fix.

@gribozavr

This comment has been minimized.

Show comment
Hide comment
@gribozavr

gribozavr May 15, 2016

Collaborator

@swift-ci Please test and merge

Collaborator

gribozavr commented May 15, 2016

@swift-ci Please test and merge

@gribozavr

This comment has been minimized.

Show comment
Hide comment
@gribozavr

gribozavr May 16, 2016

Collaborator

CI failure is unrelated, merging.

Collaborator

gribozavr commented May 16, 2016

CI failure is unrelated, merging.

@gribozavr gribozavr merged commit bebe750 into apple:master May 16, 2016

1 check failed

Test and Merge Build finished.
Details

@russbishop russbishop deleted the russbishop:se0032 branch May 19, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment