The Object-Oriented Programming Language RubyDe Nederlandse Ruby Homepage




5. Iterators



5.1 Wat is een iterator?

Een interator is een methode die een block of een Proc object als parameter heeft. In de source word het block direct na de methode aanroep geplaatst. Iterators worden gebruikt om gebruikers gestuurde controle structuren mogelijk te maken; m.n. loops.

Laten we eens naar een voorbeeld kijken om te zien hoe dit werkt. Iterators worden vaak gebruikt om een bepaalde actie te laten uitvoeren voor ieder element van een verzameling b.v.:

   
data = [1, 2, 3]
data.each do |i|
print i, "\n"
end

Produces:

1
2
3

De each methode van het array data kijgt het do...end block doorgegeven en voert dat vervolgens herhaaldelijk uit. Bij iedere aanroep worden aan het block alle opeenvolgende elementen van het array doorgegeven.

Je kan blocks ook definieeren met {...} in plaats vando...end.

   
data = [1, 2, 3]
data.each { |i|
print i, "\n"
}

Produces:

1
2
3

Deze code doet het zelfde als die in het vorige voorbeeld. This code has the same meaning as the last example. Erchter in sommige gevallen zorgen precedence issues ervoor dat do...end en{...} zich anders gedragen.

   
foobar a, b do .. end # foobar is the iterator.
foobar a, b { .. } # b is the iterator.

Dit komt omdat {...} sterker gebonden is aan de voorgaande expressie dan een do block. Het eerste voorbeeld is equivalent metfoobar(a, b) do..., het tweede met foobar(a, b {...}).

5.2 Hoe geef ik een block door aan een iterator?

Je zet het block gewoon achter de aanroep van de iterator. Je kan ook een Proc object doorgeven door een de variabele of constante die naar het block verwijst vooraf telaten gaan door een &.

5.3 Hoe word een block gebruikt in een iterator?

Er zijn drie manieren om een block in een iterator uit te voeren: (1) met het yield statement, (2) het aan roepen van een Proc parameter met call en (3) het geruik van Proc.new gevolgd door een call.

Het yield statement roept het block aan, en geeft indien nodig parameters door.

   
def myIterator
yield 1,2
end
myIterator { |a,b| puts a, b }

Produces:

1
2

Als een methode definitie een block als parameter heeft (de laatste formele parameter word vooraf gegaan door een ampersand (&)), dan word het meegegeven block omgezt in een a Proc object. Dit kan worden aangeroeven met method.call(args...).

   
def myIterator(&b)
b.call(2,3)
end
myIterator { |a,b| puts a, b }

Produces:

2
3

Proc.new (of de equivalente proc of lambda aanroepen) gebruikt in een iterator definition, neemt het gegeven block en maakt daar een procedure object van. (proc and lambda are effectively synonyms.)

   
def myIterator
Proc.new.call(3,4)
proc.call(4,5)
lambda.call(5,6)
end
myIterator { |a,b| puts a, b }

Produces:

3
4
4
5
5
6

Misschien verassend maar Proc.new and friends doen niets met het door gegeven block; iedere aanroep vanProc.new maakt een nieuw procedure object van dat zelfde block.

Je kan opvragen of er een block is doorgegeven aan een methode door het aanroepen vanblock_given?.

5.4 What doet Proc.new zonder block?

Proc.new zonder block kan geen procedure object aanmaken en geeft dus een foutmelding. In een methode definitie, echter impliceerdProc.new zonder block het bestaan van een block op het moment dat de methode word aangeroepen zodat er geen foutmelding word gegeven.

5.5 How kan ik iterators parallel laten runnen?

De oplossing van Matz's in [ruby-talk:5252], gebruikt threads:

   
  require 'thread'

def combine(*args)
queues = []
threads = []
for it in args
queue = SizedQueue.new(1)
th = Thread.start(it, queue) do |i,q|
self.send(it) do |x|
q.push x
end
end
queues.push queue
threads.push th
end
loop do
ary = []
for q in queues
ary.push q.pop
end
yield ary
for th in threads
return unless th.status
end
end
end
public :combine

def it1 ()
yield 1; yield 2; yield 3
end

def it2 ()
yield 4; yield 5; yield 6
end

combine('it1','it2') do |x|
# x is (1, 4), then (2, 5), then (3, 6)
end