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.

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).

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).

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.

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.


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.


On Thu, 18 Sep 2025 at 11:35 AM, Rene Paul Mages (ramix) via Pharo-users <
pharo-users@lists.pharo.org> wrote:

> Hello,
>
> Please give me a hint to understand the "do: separatedBy:" keyword
> message used in ProfStef tutorial :
>
>   i) in Pharo version 12 :
>
>     https://ramix.org/pharo/ProfStef/iterators23.png
>
> ii) in Pharo version 13 :
>
>     https://ramix.org/pharo/ProfStef/iterators25.png
>
> --
> Thanks for your help.
> Rene Paul Mages (ramix) GnuPG key : 0x9840A6F7
> https://sites.google.com/view/les-logiciels-libres/pharo
> https://twitter.com/RenePaulMages
>
>
>

Reply via email to