palimondo
(Pavol Vaskovic)
November 14, 2018, 1:05am
22
I'm specifically thinking about lazy method chaining, that allows the compiler to fuse them all into single loop. I don't have examples that include dropFirst, but to illustrate what I have in mind:
func slpwpc(c: ℂ) -> Int {
return sequence(first: ℂ(0), next: {z in z * z + c})
.lazy
.prefix(while: {$0.isPotentiallyInSet()})
.prefix(maxIter)
.count()
}
It is 33x slower then the imperative baseline.
Or a little bit of manual fusion of closures:
This is, in my opinion, the most “swifty” expression of the Mandelbrot renderer:
func mandelbrot_(_ c: ℂ) -> Int {
return sequence(first: ℂ(0), next: {z in
guard z.isPotentiallyInSet() else {return nil}
return z * z + c
}).lazy.prefix(maxIter).count()
}
It fuses one limiting bound of the infinite sequence into the inline closure with the guard statement and leaves the other one for the prefix method. The closure for next parameter is capturing the value of c . We’re using the first variant of sequence that internally contains another capturing closure . Using the master dev toolchain snapshot from May 30, 2018, this is 28x slower than imperative version. But as we’ll see later, this is mainly due to prefix implementation’s use of AnySequence .
When I replaced prefix with my implementation :
I was finally able to cobble together a composed “swifty” version that performs on par with the imperative baseline :
func mandelbrotL(_ c: ℂ) -> Int {
return sequence(first: ℂ(0), next: {z in
guard z.isPotentiallyInSet() else {return nil}
return z * z + c
}).lazy.prefixL(maxIter).count()
}
If you think this looks familiar, you’re right. Only difference from mandelbrot_ from above is the use of .prefixL — a proper LazySequence implementation of the method like those from SE-0045. Quite anticlimactic, if I may say.
I'd love to verify that #PR 20221 matches that performance. Can you please provide us with downloadable toolchain?