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