It's worth spending a paragraph comparing Ruby's approach to iterators
to that of C++ and Java. In the Ruby approach, the iterator is simply
a method, identical to any other, that happens to call
yield
whenever it generates a new value. The thing that uses the iterator is
simply a block of code associated with this method. There is no need
to generate helper classes to carry the iterator state, as in Java and
C++. In this, as in many other ways, Ruby is a transparent
language.
When you write a Ruby program, you concentrate on getting
the job done, not on building scaffolding to support the language
itself.
Iterators are not limited to accessing existing data in arrays and
hashes. As we saw in the Fibonacci example, an iterator can return
derived values. This capability is used by the Ruby input/output
classes, which implement
an iterator interface returning successive lines (or bytes) in an I/O
stream.
f = File.open("testfile")
f.each do |line|
print line
end
f.close
|
produces:
This is line one
This is line two
This is line three
And so on...
|
Let's look at just one more iterator implementation. The Smalltalk
language also supports iterators over collections. If you ask
Smalltalk programmers to sum the elements in an array, it's likely that
they'd use the
inject
function.
sumOfValues "Smalltalk method"
^self values
inject: 0
into: [ :sum :element | sum + element value]
|
inject
works like this. The first time the associated block
is called,
sum
is set to
inject
's parameter (zero in this case),
and
element
is set to the first element in the array. The second
and subsequent times the block is called,
sum
is set to the
value returned by the block on the previous call. This way,
sum
can be used to keep a running total. The final value of
inject
is the
value returned by the block the last time it was called.
Ruby does not have an
inject
method, but
it's easy to write one. In this case we'll add it to the
Array
class, while on page 100 we'll see how to make it more
generally available.
class Array
|
def inject(n)
|
each { |value| n = yield(n, value) }
|
n
|
end
|
def sum
|
inject(0) { |n, value| n + value }
|
end
|
def product
|
inject(1) { |n, value| n * value }
|
end
|
end
|
[ 1, 2, 3, 4, 5 ].sum
|
� |
15
|
[ 1, 2, 3, 4, 5 ].product
|
� |
120
|
Although blocks are often the target of an iterator, they also have
other uses. Let's look at a few.