Every time we use methods like map
, collect
, select
, each
we create an enumerator.
Because of enumerators, methods can be chained like
[1, 2, 3].map { .... }.select { .... }
Calling each
or select
returns an enumerator as shown below:
[1, 2, 3].each
=> #<Enumerator: ...>
[1, 2, 3].select
=> #<Enumerator: ...>
Normal Enumerator
Let’s say, we have to fetch 10 Twitter user profiles, who
have @rails
in their profile bio.
Assume, we have 1000 twitter user IDs. We may follow this approach to fetch the 10 users:
# Array of twitter user id's
twitter_user_ids = [...]
twitter_user_ids.map { |user_id| TwitterClient.user(user_id) }
.select { |data| data[:description].includes?("@rails") }
.first(10)
We iterated over 1000 users, even if the first 10 users had @rails
in
their bio.
Lazy Enumerator
The above code can be improved by adding lazy
as shown below:
# Array of twitter user id's
twitter_user_ids = [...]
twitter_user_ids.lazy
.map { |user_id| TwitterClient.user(user_id) }
.select { |data| data[:description].includes?("@rails") }
.first(10)
Here, we fetch users one-by-one without iterating over all 1000 users at once. The loop ends, when 10 users are fetched with the required condition.
Note:
Most Enumerable
methods can be called with or without a block,
but Enumerator::Lazy
will always require a block.
[1, 2, 3].map
#=> #<Enumerator: ...>
[1, 2, 3].lazy.map
# ArgumentError: tried to call lazy map without a block