compactMap on sequence() not lazy?

徘徊边缘 提交于 2020-02-02 11:18:06

问题


Every once in a while I have to walk up the responder chain to reach an instance of a known class. (Just accept this for purposes of the question.) I've been doing this with a while loop, but it occurred to me that it would be cooler to use sequence(), which can express the responder chain itself neatly like this:

let chain = sequence(first: someView as UIResponder) {$0.next}

That's brilliant because so far we haven't actually done any walking; the sequence is lazy and the anonymous function won't be executed until we start asking for elements. To prove that, let me instrument that code with a print statement:

let chain = sequence(first: someView as UIResponder) {r in print(r); return r.next}

Okay, so let's say I'm looking for the first ViewController instance in the chain. I can find it like this:

if let vc = (chain.first {$0 is ViewController}) as? ViewController {
    print(vc)
}

The printout shows that laziness is maintained: we walked up the responder chain until we got to the ViewController and stopped. Perfect! Inside the curly braces, vc is typed as ViewController and we're off to the races.

It will not have escaped your attention, however, that that's ugly. I'm both testing and casting. Is there a way I can just cast without testing and still end up with a ViewController?

This is elegant and it works fine:

for case let vc as ViewController in chain {
    print(vc)
    break
}

That's lovely and laziness is maintained — but I have to remember to say break at the end, which sort of ruins everything.

Okay, so I was really hopeful when I thought of this:

if let vc = (chain.compactMap{ $0 as? ViewController }.first) {
    print(vc)
}

It works in the sense that it compiles and gets the right answer and looks nice, but I've lost laziness. The entire chain is being traversed. Does compactMap lose laziness? Is there a way to get it back? (Or is there some other elegant way that has completely escaped me?)


回答1:


The issue is not compactMap, per se. There are two issues:

  1. If you want the sequence to call compactMap lazily, you need to use lazy.

  2. It would appear that first is preventing the lazy behavior. If you use first(where:), though, you do enjoy the lazy behavior.

Thus, while somewhat inelegant, the following achieves what you’re looking for:

if let vc = (chain.lazy.compactMap { $0 as? ViewController }.first { _ in true } ) {
    ...
} 

Or, as you say, you can implement first (or lazyFirst) on Sequence:

extension Sequence {
    var first: Element? {
        return first { _ in true }
    }
}

And then this more simplified rendition now is still lazy:

if let vc = chain.lazy.compactMap({ $0 as? ViewController }).first {
    ...
} 


来源:https://stackoverflow.com/questions/56782134/compactmap-on-sequence-not-lazy

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!