OK, so this has been more theoretically cool than actually cool, partly because this is all a bit of a hassle to use. I’m man enough to admit that. A lot of the problem is that we have to go through three steps (defining the method, making the proc, and calling the method with the proc) when it sort of feels like there should be only two (defining the method and passing the block of code right into the method, without using a proc at all), since most of the time you don’t want to use the proc/block after you pass it into the method.
It should be…more like how iterators work! Sho’ nuff, baby:
class Array |
|
def each_even(&was_a_block__now_a_proc) |
# We start with "true" because |
# arrays start with 0, which is even. |
is_even = true |
|
self.each do |object| |
if is_even |
was_a_block__now_a_proc.call object |
end |
|
# Toggle from even to odd, or odd to even. |
is_even = !is_even |
end |
end |
|
end |
|
fruits = ['apple', 'bad apple', 'cherry', 'durian'] |
fruits.each_even do |fruit| |
puts "Yum! I just love #{fruit} pies, don't you?" |
end |
|
# Remember, we are getting the even-numbered *elements* |
# of the array, which in this case are all odd numbers, |
# because I live only to irritate you. |
[1, 2, 3, 4, 5].each_even do |odd_ball| |
puts "#{odd_ball} is NOT an even number!" |
end |
Yum! I just love apple pies, don't you? |
Yum! I just love cherry pies, don't you? |
1 is NOT an even number! |
3 is NOT an even number! |
5 is NOT an even number! |
To pass in a block to each_even, all we had to do was stick the block after the method. You can pass a block into any method this way, though many methods will just ignore the block. In order to make your method not ignore the block but grab it and turn it into a proc, put the name of the proc at the end of your method’s parameter list, preceded by an ampersand (&). So, that part is a little tricky but not too bad, and you have to do that only once (when you define the method). Then you can use the method over and over again, just like the built-in methods that take blocks, such as each and times. (Remember 5.times do…? What a cutie….)
If you get confused (I mean, there’s this each and its block inside each_even), just remember what each_even is supposed to do: call the block passed in with every other element in the array. Once you’ve written it and it works, you don’t need to think about what it’s actually doing under the hood (“which block is called when?”); in fact, that’s exactly why we write methods like this—so we never have to think about how they work again. We just use them.
I remember one time I wanted to profile some code I was writing; you know, I wanted to time how long it took to run. I wrote a method that takes the time before running the code block, then runs it, then takes the time again at the end, and finally figures out the difference. And it went a little something like this:
def profile block_description, &block |
start_time = Time.new |
block.call |
duration = Time.new - start_time |
puts "#{block_description}: #{duration} seconds" |
end |
|
profile '25000 doublings' do |
number = 1 |
|
25000.times do |
number = number + number |
end |
|
puts "#{number.to_s.length} digits" |
# That's the number of digits in this HUGE number. |
end |
|
profile 'count to a million' do |
number = 0 |
1000000.times do |
number = number + 1 |
end |
end |
7526 digits |
25000 doublings: 0.044003 seconds |
count to a million: 0.072004 seconds |
How simple! How elegant! Dude, admit it: you think I’m cool. With that tiny method, we can now easily time any section of any program; we just throw the code in a block, send it to profile, and do a little dance…. What could be simpler? Though we didn’t do it, you could find the slow parts of your code and add more profiling calls nested inside your original calls! Beautiful! In most languages, I would have to explicitly add that timing code (the stuff in profile) around every section I wanted to time. What a hassle. In Ruby, however, I get to keep it all in one place and (more important) out of my way!
18.218.78.102