We've already seen how you can associate a block with a method call.
listBones("aardvark") do |aBone|
# ...
end
|
Normally, this is perfectly good enough---you associate a fixed block
of code with a method, in the same way you'd have a chunk of code
after an
if
or
while
statement.
Sometimes, however, you'd like to be more flexible. For example, we
may be teaching math skills.
[Of course, Andy and Dave would
have to learn math skills first. Conrad Schneiker reminded us that there are three kinds
of people: those who can count and those who can't.] The student
could ask for an
n-plus table or an
n-times table. If
the student asked for a 2-times table, we'd output 2, 4, 6, 8, and
so on. (This code does not check its inputs for errors.)
print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i
if times =~ /^t/
puts((1..10).collect { |n| n*number }.join(", "))
else
puts((1..10).collect { |n| n+number }.join(", "))
end
|
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20
|
This works, but it's ugly, with virtually identical code on each branch
of the
if
statement. If would be nice if we could factor out the
block that does the calculation.
print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i
if times =~ /^t/
calc = proc { |n| n*number }
else
calc = proc { |n| n+number }
end
puts((1..10).collect(&calc).join(", "))
|
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20
|
If the last argument to a method is preceded by an ampersand, Ruby
assumes that it is a
Proc
object. It removes it from the parameter
list, converts the
Proc
object into a block, and
associates it with the method.
This technique can also be used to add some syntactic sugar to block
usage. For example, you sometimes want to take an
iterator and store each value it yields into an array. We'll reuse our
Fibonacci number generator from page 40.
a = []
|
fibUpTo(20) { |val| a << val }
|
� |
nil
|
a.inspect
|
� |
"[1, 1, 2, 3, 5, 8, 13]"
|
This works, but our intention isn't quite as transparent as we may
like. Instead, we'll define a method called
into
, which
returns the block that fills the array. (Notice at the same time that
the block returned really is a closure---it references the parameter
anArray even after method
into
has returned.)
def into(anArray)
|
return proc { |val| anArray << val }
|
end
|
|
fibUpTo 20, &into(a = [])
|
a.inspect
|
� |
"[1, 1, 2, 3, 5, 8, 13]"
|