Le 18/09/2025 à 10:30, Richard O'Keefe via Pharo-users a écrit :
Thank you Richard and Tim for your help.
The ANSI Smalltalk standard has this to say:
do: operation separatedBy: separator
For each element of the receiver, operation is evaluated with the element
as the parameter.
Before evaluating operation the second and subsequent times, evaluate
separator.
Separator is not evaluated if there are no elements or after the last
element.
I understand now.
We could implement this in Collection like so:
do: doBlock separatedBy: separatorBlock
|started|
started := false.
self do: [:each |
started ifTrue: [separator value] ifFalse: [started := true].
doBlock value: each].
You can look at the actual source code by typing
do:separatedBy:
in a Playground (Workspace) and then typing Ctrl-M (show iMplementors).
Ok.
The idea is simple enough: aCollection do: aBlock separatedBy:
separatorBlock
invokes (aBlock value: x1) ... (aBlock value: xn) where x1 ... xn are the
elements of aCollection, but *between* each pair of calls to aBlock there is
an invocation of (separatorBlock value).
Now I understand the idea.
A good thing to do after you have looked at the implementation(s) is to look
at how it's used. Type do:separatedBy:
into a Playground and type Ctrl-N (show seNders). It's almost always
printing or concatenating with a separator.
And that is EXACTLY what is happening in the ProfStef tutorial you show.
Perfect.
Let's take a simple example.
Suppose you have a string 'abcd'.
If you want to write /a/b/c/d
string do: [:each | aStream nextPut: $/; nextPut: each].
If you want to write a/b/c/d/
string do: [:each | aStream nextPut: each; nextPut: $/].
So far so good, there are exactly as many / writes as there are element
writes,
so they can go in the same block. But if you want to write a/b/c/d what do
you do?
Now you have one FEWER separator than elements.
string do: [:each | aStream nextPut: each] separatedBy: [aStream nextPut:$/]
does the trick. You could of course bodge something together yourself,
BUT #do:separatedBy: is a well known idiom so it is much easier for people
to understand.
It is clear now.
Another approach would have been
Collection >>doFirst: firstBlock thenDo: otherBLock
|started|
started := false.
self do: [:each |
started ifTrue: [otherBlock value: each] ifFalse: [firstBlock value:
each. started := true]].
and then we could write
aString doFirst: [:first | aStream nextPut: first] thenDo: [:each |
aStream nextPut: $/; nextPut: each]
but that would involve code duplication, so we use #do:separatedBy: instead.
Your explanation is _very_ complete.
--
Thanks for your help.
Rene Paul Mages (ramix) GnuPG : 0x9840A6F7
https://renemages.wordpress.com/smalltalk/pharo
https://sites.google.com/view/les-logiciels-libres/pharo
https://twitter.com/RenePaulMages