In this session, I'll talk about iterators and collections.
We'll see how to easily browse collections thanks to iterators in Pharo.
You'll understand the power of iterators in Pharo.
I'll review the main iterators you may use.
An example first:
this is the code you should write in Java
to browse a collection.
I'll browse the collection persons
to extract the list of people (object persons).
In Pharo, you'd write this code.
You'd use the iterator collect. We'll talk about it later.
You collect all the names of people.
By the way, in Java 8, the latest version of Java,
they've introduced blocks,
the equivalent of lexical closures.
As a result, the syntax is close to Pharo's.
But in Pharo, we've had it since the beginning.
It's at the heart of the language.
It gives big power of expression to a programmer.
There are plenty of iterators.
First, there's collect. What's the use of collect when sent to a collection?
This is a collection of positive and negative numbers.
I send the message collect, and I pass a block.
Whenever you browse the collection,
the parameter of the block will be in turn: 2, -3, 4, etc.
Then you send the message abs, for absolute.
It means you want the absolute value of this number.
Once you've applied the block to every element of the collection,
you aggregate the results in a new one.
The result returned by collect is a new collection.
The block has been applied to every element of the collection:
the absolute value of 2, the absolute value of -3, which is 3,
the absolute value of 4, 4, the absolute value of -35, 35.
and the absolute value of 4, 4.
The interesting thing which you must remember
is that you must think object.
We ask the collection to do something for us.
The collection browses its own elements by itself.
We provide the processing of each element.
That's where the secret of iterators lies.
This is another example of collect.
This is a collection to which I send the message collect.
In the block, I'll ask every time
whether the element is odd.
I'll aggregate all the results:
false, true, false, true.
Of course, you can write
what you'd usually write in other languages
devoid of blocks and iterators.
You could write: I take the collection,
I build a collection of results.
I'll browse from one to the collection size.
I'll use do.
I browse the collection and add things in the collection result.
You could write all this. It's exactly the same.
It's up to you. Do you want to write something simple
or something complicated? That's the question.
I much prefer the first solution.
In the hierarchy of the Pharo collections,
there's something crucial: all collections are polymorphic
and inherit the class. So you have a common API.
The upside is that the iterators will also work
with most collections.
It's really about thinking object:
you tell the collection to iterate on itself.
We don't know whether we're using a dictionary.
We don't want to know about the internal logic of keys, values, etc.
So you ask the collection to be nice
and to process its elements.
Many iterators can be used to do this. We already saw do: and collect:.
There are more: select:, reject:, detect:, etc.
We'll study a few of these later in this course.
do: is the simplest iterator.
It browses every element of the collection: they're in the transcript.
We already studied it many times.
This is a new iterator: select:.
I want to get all the elements of the collection
which match a criterion.
I want all the odd elements of the collection.
I send select: to the collection.
I pass a block.
Whenever the value of the block is true,
the element concerned is added to the result collection.
It's exactly the same as select: #odd.
When I have a block here
or what amounts to a single message send to the element of the collection,
the block's parameter,
I can display the name of the message to be sent as a symbol.
It's even shorter.
It only works with unary messages.
You may use other types of iterators such as reject:.
I want to get rid of the odd elements of the collection.
In the results, I'll only have even elements left.
Or I want to use detect:.
I want to detect the first element that matches a criterion:
the value of the block must be true.
I want the first odd element of the collection: 11.
Sometimes, you want to detect the first element that matches a criterion.
If there isn't any, you want a default value:
detect: ifNone:.
If there is no match, it will return the value of this block,
which is 0.
There are other iterators which make a programmer's life easier.
For instance, anySatisfy:
tests if one object meets the criterion.
I can test if all objects meet the criterion.
I can browse the collection in reverse, from the end.
I can browse the collection with an index or browse its elements in pairs.
I can browse all the possible circular permutations, etc.
There are many iterators.
You can build new ones, too.
I want to browse one collection 1 2 3
as well as another collection.
In the block do:, I have :x and :y which are two parameters.
:x is an element of the first collection;
y:, of the second.
I can multiply these elements:
the results are 10, 40, and 90.
Of course, with this iterator, the two collections must be of the same length.
There are other ways:
here I use do: separatedBy:.
I have a collection. I browse every element.
Whenever one element is browsed,
I give value to one block which matches a comma.
So I can browse a,
display a comma, then b, then a comma, then c.
Between each element, I perform one action.
This is the iterator groupedBy:.
I can use it to group the elements of a collection
with respect to criteria.
I sent this message to the collection 1 2 3 4 5 6 7.
As a result, I get a dictionary:
all the elements which returned false to this criterion,
which was #even, the even elements.
As you can see, I have all the odd elements.
All the even elements returned true.
It's often the case, when you compute,
that you tend to nest collections in collections.
You end up with huge nesting levels.
This is an example built by hand.
These are collections nested in collections.
We'd like to flatten the collection: to even everything out.
In Pharo, there's an easy way:
the flatCollect: iterator.
I browse the elements and build a new collection where everything is even.
You end up with the 1 2 3 4 5 6 collection.
The nesting levels are all gone.
I don't intend to tell you about every Pharo iterator.
It'd be long and unpleasant. I just want to show there are many.
You can define your own
by reading about collection classes.
Find out about them.
For instance, start with the iterators you already know.
You can wonder how do: is implemented.
I look for it in the hierarchy of collections.
I realize it's implemented as SequenceableCollection.
The method do: selects aBlock as its parameter.
This is the collection's implementation.
1 to: self size do: [i:| aBlock.
I evaluate the block used as the parameter
by passing it the element i.
It's very easy.
In Pharo, iterators are very powerful, as we just saw.
Every collection supports the iterators polymorphically.
Programmers use iterators
which get implemented by collection classes
according to the collection they represent.
You can define new ones. It's very interesting.
I can define my own iterators on the collection classes.
There's a small nuance for those who know the iterator design pattern:
a developer cannot choose when to go to the next element.
The collection decides it internally.
next isn't sent directly to the iterator.
It's a nuance for those who know the Iterator design pattern.
Iterators are very powerful.
They're a programmer's best friends.
It makes writing programs easier:
you can write short, simple, and elegant code.
It ensures
data encapsulation in a collection.