When we pass a proc into a method, we can control how, if, or how many times we call the proc. For example, let’s say we want to do something before and after some code is run:
def do_self_importantly some_proc |
puts "Everybody just HOLD ON! I'm doing something..." |
some_proc.call |
puts "OK everyone, I'm done. As you were." |
end |
|
say_hello = Proc.new do |
puts 'hello' |
end |
|
say_goodbye = Proc.new do |
puts 'goodbye' |
end |
|
do_self_importantly say_hello |
do_self_importantly say_goodbye |
Everybody just HOLD ON! I'm doing something... |
hello |
OK everyone, I'm done. As you were. |
Everybody just HOLD ON! I'm doing something... |
goodbye |
OK everyone, I'm done. As you were. |
Maybe that doesn’t appear particularly fabulous…but it is. It’s all too common in programming to have strict requirements about what must be done when. Remember opening and closing a file? If you want to save or load a file, you have to open the file, write or read the relevant data, and then close the file. If you forget to close the file, Bad Things can happen. But each time you want to save or load a file, you have to do the same thing: open the file, do what you really want to do, and then close the file. It’s tedious and easy to forget. But with this trick, it’s not even an issue.
You can also write methods that will determine how many times (or even whether) to call a proc. Here’s a method that will call the proc passed in about half of the time and another that will call it twice:
def maybe_do some_proc |
if rand(2) == 0 |
some_proc.call |
end |
end |
|
def twice_do some_proc |
some_proc.call |
some_proc.call |
end |
|
wink = Proc.new do |
puts '<wink>' |
end |
|
glance = Proc.new do |
puts '<glance>' |
end |
|
maybe_do wink |
maybe_do wink |
maybe_do glance |
maybe_do glance |
twice_do wink |
twice_do glance |
These are some of the more common uses of procs that enable us to do things we simply could not have done using methods alone. Sure, you could write a method to wink twice, and you could do it with your left pinky! But you couldn’t write one to just do something twice.
Before we move on, let’s look at one last example. So far the procs we have passed in have been fairly similar to each other. This time they will be quite different, so you can see how much such a method depends on the procs passed into it. Our method will take some object and a proc and will call the proc on that object. If the proc returns false, we quit; otherwise, we call the proc with the returned object. We keep doing this until the proc returns false (which it had better do eventually, or the program will crash). The method will return the last non-false value returned by the proc.
def do_until_false first_input, some_proc |
input = first_input |
output = first_input |
|
while output |
input = output |
output = some_proc.call input |
end |
|
input |
end |
|
build_array_of_squares = Proc.new do |array| |
last_number = array.last |
if last_number <= 0 |
false |
else |
# Take off the last number... |
array.pop |
# ...and replace it with its square... |
array.push last_number*last_number |
# ...followed by the next smaller number. |
array.push last_number-1 |
end |
end |
|
always_false = Proc.new do |just_ignore_me| |
false |
end |
|
puts do_until_false([5], build_array_of_squares).inspect |
|
yum = 'lemonade with a hint of orange blossom water' |
puts do_until_false(yum, always_false) |
[25, 16, 9, 4, 1, 0] |
lemonade with a hint of orange blossom water |
OK, so that was a pretty weird example, I’ll admit. But it shows how differently our method acts when given very different procs. (Do yourself a favor, and try that lemonade. Unbelievable.)
The inspect method is a lot like to_s, except the string it returns tries to show you the Ruby code for building the object you passed it. Here it shows us the whole array returned by our first call to do_until_false. Also, you might notice that we never actually squared that 0 on the end of that array, but since 0 squared is still just 0, we didn’t have to do this. And since always_false was, you know, always false, do_until_false didn’t do anything at all the second time we called it; it just returned what was passed in.
3.145.69.53