How to Fix the Yield Command Will Not Work in Ruby
So there I was, reading The Well-Grounded Rubyist (a fantastic book, by the way!), trying to implement my own versions of Ruby’s each and map methods. The code looked simple enough. I added my_each to the Array class, and then wrote my_map to use my_each with a block. Here’s what I had:
class Array
def my_each
c = 0
until c == size
yield self[c]
c += 1
end
self
end
def my_map
acc = []
my_each { |e| acc << yield e } # This line broke everything!
acc
end
end
But when I ran it, I got this infuriating error:
page185a.rb:14: syntax error, unexpected local variable or method, expecting '}'
my_each { |e| acc << yield e }
The error pointed to the yield e part. Why? I thought I followed the book exactly! I tried splitting the code into different lines, rewriting the block—nothing worked. Turns out, the issue was something I’d never considered: Ruby’s parser gets confused when yield is used with certain operators like <<.
The Fix That Saved My Sanity
After hours of Googling and yelling at my screen, I found the solution: wrap yield e in parentheses. Yes, really. That’s it.
Here’s the corrected my_map method:
def my_map
acc = []
my_each { |e| acc << (yield e) } # Parentheses fix the ambiguity!
acc
end
Why does this work?
Ruby’s parser thought I was trying to call << yield as a method on e, which makes no sense. Adding parentheses clarifies that yield e is a single value being passed to <<. Lesson learned: when in doubt, add parentheses to yield in blocks!
Now, this works perfectly:
names = ["David", "Alan", "Black"]
p names.my_map { |name| name.upcase } # => ["DAVID", "ALAN", "BLACK"]
Leveling Up Adding More Methods
Once I fixed the yield issue, I decided to flex my new Ruby skills by adding more methods inspired by Ruby’s Enumerable module. Here’s what I built:
my_map Without a Block Returns an Enumerator
Ruby methods like map return an Enumerator if no block is given. Let’s replicate that:
def my_map
return to_enum(:my_map) unless block_given? # No block? Return Enumerator!
acc = []
my_each { |e| acc << (yield e) }
acc
end
Example:
map_enum = [1, 2, 3].my_map
p map_enum.each { |n| n * 2 } # => [2, 4, 6]
In-Place my_map! Method
Why not modify the array directly, like map!?
def my_map!
return to_enum(:my_map!) unless block_given?
my_each_with_index { |e, i| self[i] = yield(e) } # Update the array in place
self
end
Example:
numbers = [1, 2, 3]
numbers.my_map! { |n| n * 10 }
p numbers # => [10, 20, 30]
Filtering with my_select
Let’s filter elements based on a block condition:
def my_select
return to_enum(:my_select) unless block_given?
acc = []
my_each { |e| acc << e if yield(e) } # Collect elements where the block is truthy
acc
end
Example:
p [10, 20, 30, 40].my_select { |n| n > 25 } # => [30, 40]
The Swiss Army Knife: my_reduce
This one was tricky! I wanted to replicate reduce/inject, supporting both symbols (like :+) and blocks:
def my_reduce(initial = nil, sym = nil, &block)
acc = initial.nil? ? first : initial # Handle initial value
start_idx = initial.nil? ? 1 : 0
if block
my_each_with_index(start_idx...) { |e, i| acc = block.call(acc, e) }
elsif sym
my_each_with_index(start_idx...) { |e, i| acc = acc.send(sym, e) }
else
raise ArgumentError, "You must provide a block or a symbol"
end
acc
end
Examples:
p [1, 2, 3].my_reduce(:+) # => 6 (sum) p [1, 2, 3].my_reduce(10, :+) # => 16 (sum with initial value)
Final Thoughts
- Parentheses Matter with
yield: Ruby’s parser isn’t perfect. When combiningyieldwith operators like<<, clarify your intent with(yield e). - Emulate Ruby’s Patterns: Returning
Enumeratorobjects when no block is given makes your methods more flexible and idiomatic. - Iteration is Powerful: By building
my_each,my_map,my_select, andmy_reduce, I finally grasped how Ruby’s iteration methods work under the hood.
If you’re new to Ruby like me, don’t fear yield just remember to add those parentheses! And hey, try re implementing methods yourself.