Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Ruby Ruby Foundations Blocks Yield

Operating on yielded value inside block

Reassigning a variable value within a block doesn't affect the value of the variable in the method. However, operating on the variable with a method! does. See the examples below. Can anyone explain?

def get_name
    name = "Vinny"
    yield name
    puts "Nice to meet you, #{name}"
end

get_name do |name|
    puts "Hi, #{name}"
    name = name.upcase
    puts "Here is your uppercase name: #{name}"
end

Produces this:/ Hi, Vinny/ Here is your uppercase name: VINNY/ Nice to meet you, Vinny

def get_name
    name = "Vinny"
    yield name
    puts "Nice to meet you, #{name}"
end

get_name do |name|
    puts "Hi, #{name}"
    name.upcase!
    puts "Here is your uppercase name: #{name}"
end

Produces this:/ Hi, Vinny/ Here is your uppercase name: VINNY/ Nice to meet you, VINNY

1 Answer

Gavin Ralston
Gavin Ralston
28,770 Points

(Heavily edited: I am tired and wrote a pretty poor, convoluted, and not very correct answer)

tl;dr if you've worked with C: It's a pointer thing. :) If not, read on.

In both examples, you're submitting a code block (get_name do |name|.....end) for your function to yield to, so it goes like this:

  1. get_name assigns name = "Vinny"
  2. get_name yields name ("Vinny") to the code block
  3. the code block (from do |name| all the way to end) gets the location of the variable from get_name
  4. the code block assigns the location of the value in 'name'
  5. the code block calls methods on that object using it's own variable called "name" but it's still "reaching into" the variable really stored in the get_name function
  6. when the code block assigns a string to the variable 'name', it stops storing the location of the data you wanted to manipulate, and now stores the string you created.
  7. that variable then disappears when you reach the end statement of your code block

The difference is that in the first case, you're not using a destructive method (.upcase just returns a string and doesn't modify the original) and in the second, you're using .upcase! which manipulates the original value and then assigns the new string to your local variable as well.

You've got |name| in the code block, but remember this is local to the code block. You could have called it |x| or |bowling_team_member| or whatever else you wanted.

It's weird, but maybe if we change the variable names a bit it will be more evident:

def get_name
    name = "Vinny"
    yield name
    puts "nice to meet you, #{name}"
end

get_name do |person|          # person is the address of 'name' in your function
    puts "Hi, #{person}"     # gets 'Vinny' through the address of name (inside get_name)     
    name = person.upcase # creates a new string w/o modifying the original, stored locally
    puts "here is your uppercase name: #{name}"   # then prints from block-local variable
end      # then trashes 'name' as the block closes, with 'person' never modified

The output is identical to your first script. Capitalized, all upper case, Capitalized:

In the first script you grabbed the string inside get_name, used it to generate a brand new string, and changed the block variable "name" from the original string in your get_name function to a brand new string, which then vanished without ever manipulating the original text.

|name| in your code block is simply storing the address where the value (which really belongs to get_name) is being kept.

So when you actually try to assign something to that variable , you do just that, and now the variable 'name' in your code block holds its own string, and no longer references the value actually stored into the function, and can't reach back into the get_name function ever again to change the value.

Now...

get_name do |name|  # THIS 'name' simply "points to" a value inside get_name function
    puts "Hi, #{name}" # reaches into get_name, grabs value *there*, prints it
    name.upcase!          # reaches into get_name, modifies string there
    puts "Here is your uppercase name: #{name}" # reaches in, prints modified string
end   #poof, there goes our local 'name', but 'name' in get_name still exists, holding new string

In the second script, you never broke the link to the value stored in the get_name function, but just modified it directly. So you'll call a string, directly from get_name where it's stored, but this time it's actually changed in the original function. Nothing at all will look different when this block runs, but when you get back to get_name and continue, the value there was actually changed, so your final output will result in a capitalized string, too.

At first, that behavior seems really strange, especially when you use the same variable name in the code block as you do in the function, but less so if you think about what it's doing.

So here's your first script, modified, using a destructive method, where you can see that once you assigned a new string to 'name' it doesn't matter what you do to it anymore, because it no longer points back to the value actually stored in your calling function (get_name)

def get_name
    name = "Vinny"
    yield name
    puts "nice to meet you, #{name}"
end

get_name do |name|
    puts "Hi, #{name}"
    name = name.upcase!
    name = "Penochle, Dolphin, Tuna Salad."
    name.reverse!
    puts "here is your uppercase name: #{name}"
end

The output will now be

Hi, Vinny
here is your uppercase name: .dalaS anuT ,nihploD ,elhconeP
nice to meet you, VINNY

So get_name had its 'name' variable changed to upper case, because you modified it, then assigned it to the 'name' variable inside the code block.

But notice that assigning a new string to name, and name.reverse! are only applied to the string stored in the name variable located in the code block and those changes do NOT occur "back home" in the calling function.

Once you break the link to the original string, anything you do from there on out will be limited to that code block, and your function's variable can't be manipulated any longer. That's why the first script probably did what you wanted it to do, even though it isn't probably working exactly as you intended. :)