When gsub gets both a replacement and a block
Question
What is the return value of the following Ruby code?
"aaoo".gsub("a") {|c| c.upcase} # => "AAoo"
"aaoo".gsub("a", "o") {|c| c.upcase} # => ???
The correct answer is
-
"AAOO" -
"OOOO" -
"oooo"Correct -
"AAAA"
Explanation
TL;DR
String#gsub accepts either a replacement argument or a block, and when it receives both, the replacement argument wins. The block is silently ignored, no warning, no error. So every "a" is replaced with "o" and the result is "oooo".
Step by step
With only a block, gsub calls it once per match and substitutes the block's return value:
"aaoo".gsub("a") { |c| c.upcase } # => "AAoo"
With only a replacement argument, gsub substitutes it for every match:
"aaoo".gsub("a", "o") # => "oooo"
With both, the replacement argument takes precedence and the block never runs:
called = false
"aaoo".gsub("a", "o") { |c| called = true; c.upcase } # => "oooo"
called # => false
This matches the documented signatures of gsub: gsub(pattern, replacement) and gsub(pattern) { |match| ... } are two distinct forms, and the two-argument form does not yield.
Edge cases
The same precedence rule applies to sub, gsub!, and sub!. Note that this is the opposite of methods like Hash#fetch, which also accepts a default argument plus a block but prefers the block, and at least warns about the conflict (warning: block supersedes default value argument):
{}.fetch(:a, 1) { 2 } # => 2
Share this quiz
Comments
No comments yet. Be the first.