LaunchKit
All quizzes
Ruby 1 views

Lazy enumerators run the whole chain per element

Question

What is the return value of the following Ruby code?

a = []
upcase   = ->(c) { a << c; c.upcase }
downcase = ->(c) { a << c; c.downcase }

%w(a b c).lazy.map(&upcase).map(&downcase).force

a.join # => ???

The correct answer is

Explanation

TL;DR

With Enumerable#lazy, the chained map calls do not run as separate passes. Each element travels through the whole chain before the next element starts: "a" is upcased then downcased, then "b", then "c". Both lambdas log the value they receive, so a collects "aAbBcC".

Step by step

a = []
upcase   = ->(c) { a << c; c.upcase }
downcase = ->(c) { a << c; c.downcase }

%w(a b c).lazy.map(&upcase).map(&downcase).force # => ["a", "b", "c"]

a.join # => "aAbBcC"

For each element, upcase logs the original character and returns its uppercase version, then downcase logs that uppercase character and returns it lowercased. Unrolled, the lazy chain is equivalent to:

a = []
chars = %w(a b c)
new_chars = []
upcase   = ->(c) { a << c; c.upcase }
downcase = ->(c) { a << c; c.downcase }

new_chars[0] = downcase.(upcase.(chars[0])) # a << "a", then a << "A"
new_chars[1] = downcase.(upcase.(chars[1])) # a << "b", then a << "B"
new_chars[2] = downcase.(upcase.(chars[2])) # a << "c", then a << "C"

a.join # => "aAbBcC"

The eager version

Without lazy, each map is a full pass over the collection. The first map logs "a", "b", "c" and returns ["A", "B", "C"]; the second pass then logs "A", "B", "C":

a = []
upcase   = ->(c) { a << c; c.upcase }
downcase = ->(c) { a << c; c.downcase }

%w(a b c).map(&upcase).map(&downcase) # => ["a", "b", "c"]

a.join # => "abcABC"

The final result of the chain is identical; only the order of evaluation differs. That per-element order is exactly what makes lazy useful: combined with first(n) or an infinite source, it only processes the elements it needs.

(1..Float::INFINITY).lazy.map { |x| x * 2 }.first(3) # => [2, 4, 6]

Share this quiz

Comments

No comments yet. Be the first.

Only used to confirm and publish your comment. Never shown publicly, never shared.

Markdown: **bold**, `code`, ```fenced blocks```, > quotes, [links](url). HTML and images are not rendered.