Blocks for beginners
Apr. 21st, 2009 02:27 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
![[community profile]](https://www.dreamwidth.org/img/silk/identity/community.png)
Blocks are tricky to wrap your brain around and hard to explain, but they're very, very useful, so you'll probably have to tackle them sooner or later.
When it comes down to it, a block is just a bunch of code found within a pair of curly braces {} or the words 'do' and 'end'. The block itself is a bit like a sentence fragment - it's not an independent object, and it can't stand on its own. If you open up irb and type {|x| puts x} on a line, irb will just say, "dude, I think there's something wrong with your hash." What's nifty about blocks isn't so much what they are as how they can be used.
First, let's see some of your most common, garden-variety Ruby blocks.
Take an array of companions from Doctor Who:
companions = ['Rose', 'Martha', 'Donna']
Let's say they're in mortal peril (which happens pretty often, actually) and the Doctor is shouting their names, so we want to make each name uppercase and add an exclamation point to it:
companions.each {|companion| puts companion.upcase + '!'}
This will give you:
companions is an array; each is an array method that takes a block. companion is just a local variable name; we could have used x or human or raxicoricofallapatorius there and have gotten the same result. The pipes (|wheee|)just give you a place to name your variable(s). What the each method actually does is loop through your array and run the code in your block on each element in turn until it gets to the end.
It would work the same way if you did it like this:
The do/end style is usually handy if you've got more than one line of code in there.
And to make sure beginners are as confused as they can possibly be, you can also write the same code this way:
each is a bit like the Doctor himself that way - it comes in a lot of different guises, but they're all the same alien on the inside.
Note: if you're using these methods inside a Rails view, which is pretty common, you wouldn't use 'puts', you'd just do:
The erb tags (<%= %>) take care of outputting the results for you. Of course, you'd probably want to add some html to that too. And this is a case where you would want to use one of the multi-line methods instead of companions.each {|companion| companion.upcase + '!'} because the one-line block will return the original array, while the multi-line methods let you output each iteration.
Another very common array method that takes a block is collect (also known as map - it doesn't matter which name you use, it's the exact same method). Let's say you want to take your array of names and make a new array of those same names being shouted:
companions_in_danger = companions.collect {|companion| companion.upcase + '!'}
Now in addition to our original companions array, ['Rose', 'Martha', 'Donna'], we have a second array called companions_in_danger: ['ROSE!', 'MARTHA!', 'DONNA!'].
You can also use do/end here:
But for companion in companions only works for each.
Note #2: you *can* actually stick <%= companions.collect {|companion| companion.upcase + '!'} %> in a Rails view, although you'll want to make sure to add spaces, line breaks or other html. It works because the collect method returns the new array and not the original and because erb automatically converts arrays into strings, although obviously that works out best if what you have *is* an array of strings.
If Companion were a class and each companion had a name that you could access via companion.name, it might be very handy to do something like:
In fact, it's so handy, there's a shorthand for it in Rails (and coming soon/now, in Ruby 1.9):
If you're just starting out, it may be easiest to think of that as magical shorthand that turns a symbol into a block; that's not quite accurate, but we'll get to that if/when we get to procs. If you're curious, try googling "symbol to proc".
The key is inside the method definition. This is the simplest possible use of a block (so simple it's totally pointless):
would give you the exact same results as:
puts "wakka wakka" on its own. You'll notice that yield is the magic word - yield is what says "this method should have a block and this is where you should run its code."
Try using more than one yield:
If you think that will output:
you'd be right.
Things get more interesting when you start sending information to and from the block via variables:
Will give you:
"One day, the Doctor and Donna were out for a walk on an alien planet."
But if you decide you don't like that story, you could change things up a bit:
That will give you:
"One day, the Doctor and Martha were out for a walk on an alien planet."
When I hit yield(companion) in storytime, I run the code in the block and I send it the value of companion, which gets picked up by the local variable. It doesn't matter that I've named it something else. When the block finishes, the companion is still Donna, though. I've only succeeded in bringing Martha in as a guest star, unless I change the method to set the value of companion to whatever yield(companion) returns.
Now, those are things you could do just as easily with a storytime(name) method. How do methods like each and collect actually work? Might look something like this (total supposition, but they will actually run):
If I have three things in my array, I wind up calling yield three times, and each time, I send a different value to the block. I can also do things with the values I get back from the block, like add them to a new array. You're able to make your methods so much more flexible this way, and reuse a lot more code.
(I've used each and collect as examples here, but block behavior is the same for methods like select, find, sort, etc. and any other methods with blocks.)
Next up: procs and lambda! At least, as much about them as I understand myself. *cough*
When it comes down to it, a block is just a bunch of code found within a pair of curly braces {} or the words 'do' and 'end'. The block itself is a bit like a sentence fragment - it's not an independent object, and it can't stand on its own. If you open up irb and type {|x| puts x} on a line, irb will just say, "dude, I think there's something wrong with your hash." What's nifty about blocks isn't so much what they are as how they can be used.
Basic blocks
First, let's see some of your most common, garden-variety Ruby blocks.
Take an array of companions from Doctor Who:
companions = ['Rose', 'Martha', 'Donna']
Let's say they're in mortal peril (which happens pretty often, actually) and the Doctor is shouting their names, so we want to make each name uppercase and add an exclamation point to it:
companions.each {|companion| puts companion.upcase + '!'}
This will give you:
ROSE! MARTHA! DONNA!
companions is an array; each is an array method that takes a block. companion is just a local variable name; we could have used x or human or raxicoricofallapatorius there and have gotten the same result. The pipes (|wheee|)just give you a place to name your variable(s). What the each method actually does is loop through your array and run the code in your block on each element in turn until it gets to the end.
It would work the same way if you did it like this:
companions.each do |companion| puts companion.upcase + '!' end
The do/end style is usually handy if you've got more than one line of code in there.
And to make sure beginners are as confused as they can possibly be, you can also write the same code this way:
for companion in companions puts companion.upcase + '!' end
each is a bit like the Doctor himself that way - it comes in a lot of different guises, but they're all the same alien on the inside.
Note: if you're using these methods inside a Rails view, which is pretty common, you wouldn't use 'puts', you'd just do:
<%- companions.each do |companion| -%> <%= companion.upcase + '!' %> <%- end -%>
The erb tags (<%= %>) take care of outputting the results for you. Of course, you'd probably want to add some html to that too. And this is a case where you would want to use one of the multi-line methods instead of companions.each {|companion| companion.upcase + '!'} because the one-line block will return the original array, while the multi-line methods let you output each iteration.
Another very common array method that takes a block is collect (also known as map - it doesn't matter which name you use, it's the exact same method). Let's say you want to take your array of names and make a new array of those same names being shouted:
companions_in_danger = companions.collect {|companion| companion.upcase + '!'}
Now in addition to our original companions array, ['Rose', 'Martha', 'Donna'], we have a second array called companions_in_danger: ['ROSE!', 'MARTHA!', 'DONNA!'].
You can also use do/end here:
companions_in_danger = companions.collect do |companion| companion.upcase + '!' end
But for companion in companions only works for each.
Note #2: you *can* actually stick <%= companions.collect {|companion| companion.upcase + '!'} %> in a Rails view, although you'll want to make sure to add spaces, line breaks or other html. It works because the collect method returns the new array and not the original and because erb automatically converts arrays into strings, although obviously that works out best if what you have *is* an array of strings.
If Companion were a class and each companion had a name that you could access via companion.name, it might be very handy to do something like:
companion_names = companions.collect {|companion| companion.name}
In fact, it's so handy, there's a shorthand for it in Rails (and coming soon/now, in Ruby 1.9):
companion_names = companions.collect(&:name)
If you're just starting out, it may be easiest to think of that as magical shorthand that turns a symbol into a block; that's not quite accurate, but we'll get to that if/when we get to procs. If you're curious, try googling "symbol to proc".
How blocks actually work
The key is inside the method definition. This is the simplest possible use of a block (so simple it's totally pointless):
def hollow_method yield end hollow_method {puts "wakka wakka"}
would give you the exact same results as:
puts "wakka wakka" on its own. You'll notice that yield is the magic word - yield is what says "this method should have a block and this is where you should run its code."
Try using more than one yield:
def double_your_fun yield yield end double_your_fun {puts "wakka"}
If you think that will output:
wakka wakka
you'd be right.
Things get more interesting when you start sending information to and from the block via variables:
def storytime print "One day, the Doctor and " companion = "Donna" yield(companion) print " were out for a walk on an alien planet." end storytime {|name| print name }
Will give you:
"One day, the Doctor and Donna were out for a walk on an alien planet."
But if you decide you don't like that story, you could change things up a bit:
storytime do |name| name = "Martha" print name end
That will give you:
"One day, the Doctor and Martha were out for a walk on an alien planet."
When I hit yield(companion) in storytime, I run the code in the block and I send it the value of companion, which gets picked up by the local variable. It doesn't matter that I've named it something else. When the block finishes, the companion is still Donna, though. I've only succeeded in bringing Martha in as a guest star, unless I change the method to set the value of companion to whatever yield(companion) returns.
Now, those are things you could do just as easily with a storytime(name) method. How do methods like each and collect actually work? Might look something like this (total supposition, but they will actually run):
class Array def fake_each i = 0 while i < self.length do yield(self[i]) i = i + 1 end end def fake_collect new_array = [] i = 0 while i < self.length do new_array << yield(self[i]) i = i + 1 end new_array end end
If I have three things in my array, I wind up calling yield three times, and each time, I send a different value to the block. I can also do things with the values I get back from the block, like add them to a new array. You're able to make your methods so much more flexible this way, and reuse a lot more code.
(I've used each and collect as examples here, but block behavior is the same for methods like select, find, sort, etc. and any other methods with blocks.)
Next up: procs and lambda! At least, as much about them as I understand myself. *cough*
(no subject)
Date: 2009-05-26 01:02 am (UTC)The block is this (ps I love your examples!)
companions.each do |companion|
puts companion.upcase + '!'
end
What happens is that the .each takes the first name in the array and sends it to the block where the block says "you're my input and I'm going to now call you 'companion'." Then the block says "since you are a 'companion' I'm going to output you in all caps with an exclamation point." Now the block's job is done and the .each sends it the next item in the array.
Essentially the |companion| is only relevant for the block itself and once the array item leaves the block it's no longer associated with the 'companion' variable. Is that right?
ALZMkSPHuNsKmbS
Date: 2013-05-28 05:17 am (UTC)haAAAyEJAInl
Date: 2013-06-08 04:24 pm (UTC)MZQnSPFGxaa
Date: 2013-05-26 11:44 am (UTC)WpFOJWILdbeIVKevoha
Date: 2013-06-09 01:52 am (UTC)yemNBgoagrgzS
Date: 2013-06-11 03:41 pm (UTC)syyhncXLZL
Date: 2013-05-28 05:58 am (UTC)