Skip to content
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

[SR-10270] Lazy compact map invokes twice on last element #52670

Closed
swift-ci opened this issue Apr 2, 2019 · 5 comments
Closed

[SR-10270] Lazy compact map invokes twice on last element #52670

swift-ci opened this issue Apr 2, 2019 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. standard library Area: Standard library umbrella

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Apr 2, 2019

Previous ID SR-10270
Radar None
Original Reporter dbplunkett (JIRA User)
Type Bug
Status Resolved
Resolution Invalid
Environment

Xcode 10.2 (10E125)
Swift 5

Additional Detail from JIRA
Votes 0
Component/s Standard Library
Labels Bug
Assignee None
Priority Medium

md5: 6fa83fba79de904db5da8b327a2313a5

Issue Description:

let array = [-1, 0, 1, 2, 3]
let lazy = array.lazy.compactMap { (int: Int) -> Int? in
    print(int) // prints -1, 0, 1, 1
    return int > 0 ? int * 10 : nil
}
lazy.first // stop at the first non-nil mapped value - in this case, 1

Printing the input of each invocation of the closure demonstrates that it is invoked twice on the last mapped value when the mapping is applied lazily.

Expected: The printed values are -1, 0, 1
Actual: The printed values are -1, 0, 1, 1

@belkadan
Copy link
Contributor

belkadan commented Apr 2, 2019

Sorry, this is also correct. The lazy sequences in Swift do not cache their results, as a deliberate design choice.

cc @lorentey again

@swift-ci
Copy link
Collaborator Author

swift-ci commented Apr 2, 2019

Comment by David Plunkett (JIRA)

Thank you. I had a unit test that checked that a lazy operation was not applied any more times than it "needed" to be. The test passed prior to Xcode 10.2, but I guess the premise of my test was presumptuous.

@swift-ci
Copy link
Collaborator Author

Comment by Alex Johnson (JIRA)

@belkadan Are you sure this is the expected behavior? My teammate ran into this today, and we came up with the following example.

Consider this lazy collection with five chained compactMap calls:

let array = ["cat", "dog", "fish"]

let lazyArray = array.lazy
    .compactMap { i -> String? in
        print("AAA: \(i)")
        return i as String?
    }
    .compactMap { i -> String? in
        print("BBB: \(i)")
        return i as String?
    }
    .compactMap { i -> String? in
        print("CCC: \(i)")
        return i as String?
    }
    .compactMap { i -> String? in
        print("DDD: \(i)")
        return i as String?
    }
    .compactMap { i -> String? in
        print("EEE: \(i)")
        return i as String?
    }

Each transform just returns the input value, so if we convert it back to an array, we can see the each transform being executed for each element:

Array(lazyArray) // => ["cat", "dog", "fish"]

Output from print calls:

AAA: cat
BBB: cat
CCC: cat
DDD: cat
EEE: cat
AAA: dog
BBB: dog
CCC: dog
DDD: dog
EEE: dog
AAA: fish
BBB: fish
CCC: fish
DDD: fish
EEE: fish

We can also access an element by subscript and see each transform being executed once for that element:

lazyArray[0] // => "cat"

Output from print calls:

AAA: cat
BBB: cat
CCC: cat
DDD: cat
EEE: cat

And we can use an iterator:

var i = lazyArray.makeIterator()
i.next() // => Optional("cat")

Output from print calls:

AAA: cat
BBB: cat
CCC: cat
DDD: cat
EEE: cat

But, things start to get weird if we ask for the first element instead. There appears to be a diminishing loop where each transform is executed in a stacked manner.

lazyArray.first // => Optional("cat")

Output from print calls (with blank lines added to aid clarity):

AAA: cat

AAA: cat
BBB: cat

AAA: cat
BBB: cat
CCC: cat

AAA: cat
BBB: cat
CCC: cat
DDD: cat

AAA: cat
BBB: cat
CCC: cat
DDD: cat
EEE: cat

AAA: cat
BBB: cat
CCC: cat
DDD: cat
EEE: cat

There are similar execution patterns when accessing other properties of the lazy collection, like startIndex and isEmpty.

@swift-ci
Copy link
Collaborator Author

Comment by David Plunkett (JIRA)

It is pretty confusing - I didn't really understand the explanation that the results are not cached, but I figured it was just over my head. It would be cool to maybe find out some more about this behavior.

@swift-ci
Copy link
Collaborator Author

Comment by Vincent Isambart (JIRA)

Couldn't we for example add a specialization of var first: Element? to LazyMapSequence and LazyFilterSequence?

var first: Element? {
    var iterator = makeIterator()
    return iterator.next()
}

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. standard library Area: Standard library umbrella
Projects
None yet
Development

No branches or pull requests

2 participants