ChuckE
ChuckE

Reputation: 5688

Ruby Lazy Enumerable flat_map is not very lazy

Edit: Since I wrote the question with a wrong example and didn't describe my issues, I'll do it again!

It seems to me that #flat_map, even though part of the Enumerator::Lazy class, is not very enumerable itself.

This example correctly works:

(1..Float::INFINITY).flat_map { |s| [s,s] }.take(4).to_a

The lazy implementation will also work:

(1..Float::INFINITY).flat_map { |s| [s,s] }.take(4).to_a

This will only take into account that the array generated within the block are finite. But they will also be fully evaluated before the take(4) call takes place. Which is not very lazyable.

Hence, this will fail:

(1..Float::INFINITY).lazy.flat_map { |i| (i..Float::INFINITY).map(&:to_i) }.take(4).force

Because the "infinity range to array" will be fully evaluated before the lazy call takes place. I would expect it to be "lazy by default", though. I mean, I do understand where the conundrum lies, but I'd expect it to happen this way: flat_map evaluates each instance lazy, knows that the result will be an array (or enumerable, at least), and will apply the lazy mechanism on it. hence, (i..Float::INFINITY).map(&:to_i) would be lazified (which doesn't seem very compatible, since the map(&:to_i) call will "force" it to be calculated).

Upvotes: 2

Views: 1066

Answers (1)

Patrick Oscity
Patrick Oscity

Reputation: 54684

You are not actually using a lazy enumerator. To convert a normal enumerator to a lazy one, use the Enumerable#lazy method. To get the results back I'd recommend using the method Enumerable::Lazy#force instead of to_a because I think it shows the intention more clearly.

(1..Float::INFINITY).lazy.flat_map { |s| [s,s] }.take(4).force
#=> [1, 1, 2, 2]

Upvotes: 7

Related Questions