Hello all,
I'm finding myself unable to implement something seemingly simple. Here is the puzzle: I want an iterator factory which, given a specific collection, produces a block taking two arguments, also blocks. One (the doBlock) tells what should be done for each element of the collection, and the other (the whileBlock) is a test allowing to abort the whole operation. So, something like this: Puzzle>>blockIterating: aCollection ^ [:doBlock :whileBlock | aCollection do: [:i | (whileBlock value: i) ifFalse: [^ self]. doBlock value: i]]. Then I could do things like the following: | block | block := Puzzle new blockIterating: (1 to: 5). block value: [:p | Transcript show: p; cr] value: [:p | p < 3] But the above fails with a 'Block cannot return' (that's the [^ self] block in the #blockIterating: method). I have attached the code; just do "Puzzle new fail". I can't find a workaround. How should I proceed to get a working iterator block ? Stef Puzzle.st (994 bytes) Download Attachment |
Hi Stéphane,
I don't have time to work this out right now but how about taking a look at #findFirst: and seeing if you can replace your #do: with something like what #findFirst: does. Lou >Hello all, > >I'm finding myself unable to implement something seemingly simple. Here >is the puzzle: > >I want an iterator factory which, given a specific collection, produces >a block taking two arguments, also blocks. One (the doBlock) tells what >should be done for each element of the collection, and the other (the >whileBlock) is a test allowing to abort the whole operation. > >So, something like this: > >Puzzle>>blockIterating: aCollection > > ^ [:doBlock :whileBlock | > aCollection do: [:i | > (whileBlock value: i) ifFalse: [^ self]. > doBlock value: i]]. > > >Then I could do things like the following: > > >| block | > >block := Puzzle new blockIterating: (1 to: 5). > >block value: [:p | Transcript show: p; cr] value: [:p | p < 3] > > > >But the above fails with a 'Block cannot return' (that's the [^ self] >block in the #blockIterating: method). I have attached the code; just do >"Puzzle new fail". > >I can't find a workaround. > >How should I proceed to get a working iterator block ? > >Stef Louis LaBrunda Keystone Software Corp. SkypeMe callto://PhotonDemon mailto:[hidden email] http://www.Keystone-Software.com |
In reply to this post by Stéphane Rollandin
Hi! If your collection understands #readStream, you may write:Puzzle >> blockIterating: aCollection ^[ :doBlock :whileBlock | | stream item | stream := aCollection readStream. [ stream atEnd not and: [ whileBlock value: (item := stream next) ] ] whileTrue: [ doBlock value: item ] ] If it only understands #do:, you may wrap it in a Generator: Puzzle >> blockIterating: aCollection ^[ :doBlock :whileBlock | | stream item | stream := Generator on: [ :g | aCollection do: [ :each | g nextPut: each ] ]. [ stream atEnd not and: [ whileBlock value: (item := stream next) ] ] whileTrue: [ doBlock value: item ] ] If it's not a requirement to return a Block, you may create a class, whose instances respond to #value:value: and sidestep the problem. Cheers, Balázs |
Thanks for your answers.
Unfortunately in my actual use case (of which the puzzle is just the simplest representation) I am not allowed to work on the collection side (and other iterating methods than #do: can be used by the iterator factory). But actually, I think I just found a workaround: throw an exception, such as Abort which by the way seems to be unused. blockIterating: aCollection ^ [:doBlock :whileBlock | [aCollection do: [:i | (whileBlock value: i) ifFalse: [Abort signal]. doBlock value: i]] on: Abort do: []] Stef |
Here without exceptions:
list := 1 to: 10. iterator := [:doBlock :whileBlock | list detect: [:ea | | result | (result := whileBlock value: ea) ifTrue: [doBlock value: ea]. result not] ifNone: []]. Best, Marcel |
In reply to this post by Stéphane Rollandin
Returning from a block means returning from the method containing that
block. (There's no syntax for block return in Smalltalk, because you'd have to be able to tell which block you want to return from to make that be of any use, but blocks are anonymous methods. If you need a named method, then use one.). You get that error, because you've already returned from the method (the block itself was returned), and it's not possible to return twice from the same context. One easy way to make it work is to send a message from inside the block, because then you'll have a separate context to return from. E.g.: Puzzle >> #blockIterating: aCollection ^[ :doBlock :whileBlock | self iterate: aCollection do: doBlock while: whileBlock ]. Puzzle >> #iterate: aCollection do: doBlock while: whileBlock aCollection do: [ :each | (whileBlock value: each) ifFalse: [ ^self ]. doBlock value: each ] Levente On Thu, 14 May 2015, Stéphane Rollandin wrote: > Hello all, > > I'm finding myself unable to implement something seemingly simple. Here is > the puzzle: > > I want an iterator factory which, given a specific collection, produces a > block taking two arguments, also blocks. One (the doBlock) tells what should > be done for each element of the collection, and the other (the whileBlock) is > a test allowing to abort the whole operation. > > So, something like this: > > Puzzle>>blockIterating: aCollection > > ^ [:doBlock :whileBlock | > aCollection do: [:i | > (whileBlock value: i) ifFalse: [^ self]. > doBlock value: i]]. > > > Then I could do things like the following: > > > | block | > > block := Puzzle new blockIterating: (1 to: 5). > > block value: [:p | Transcript show: p; cr] value: [:p | p < 3] > > > > But the above fails with a 'Block cannot return' (that's the [^ self] block > in the #blockIterating: method). I have attached the code; just do "Puzzle > new fail". > > I can't find a workaround. > > How should I proceed to get a working iterator block ? > > Stef > |
Ah, so that's the bottom line. Thanks a lot ! I'm now trying to wrap my
head around these concepts... It looks like I could also get the return block as an outside argument, like this: Puzzle>>blockIterating: aCollection ^ [:returnBlock | [:doBlock :whileBlock | aCollection do: [:i | (whileBlock value: i) ifFalse: returnBlock. doBlock value: i]]]. and then use it as follow: | block | block := (Puzzle new blockIterating: (1 to: 5)) value: [ ^self]. block value: [:p | Transcript show: p; cr] value: [:p | p < 3] A bit hard to read but it seems to work. Does that make sense ? Stef > Returning from a block means returning from the method containing that > block. (There's no syntax for block return in Smalltalk, because you'd > have to be able to tell which block you want to return from to make that > be of any use, but blocks are anonymous methods. If you need a named > method, then use one.). > You get that error, because you've already returned from the method (the > block itself was returned), and it's not possible to return twice from the > same context. > One easy way to make it work is to send a message from inside the block, > because then you'll have a separate context to return from. > E.g.: > > Puzzle >> #blockIterating: aCollection > > ^[ :doBlock :whileBlock | > self iterate: aCollection do: doBlock while: whileBlock ]. > > Puzzle >> #iterate: aCollection do: doBlock while: whileBlock > > aCollection do: [ :each | > (whileBlock value: each) ifFalse: [ ^self ]. > doBlock value: each ] > > Levente > > On Thu, 14 May 2015, Stéphane Rollandin wrote: > >> Hello all, >> >> I'm finding myself unable to implement something seemingly simple. Here is >> the puzzle: >> >> I want an iterator factory which, given a specific collection, produces a >> block taking two arguments, also blocks. One (the doBlock) tells what should >> be done for each element of the collection, and the other (the whileBlock) is >> a test allowing to abort the whole operation. >> >> So, something like this: >> >> Puzzle>>blockIterating: aCollection >> >> ^ [:doBlock :whileBlock | >> aCollection do: [:i | >> (whileBlock value: i) ifFalse: [^ self]. >> doBlock value: i]]. >> >> >> Then I could do things like the following: >> >> >> | block | >> >> block := Puzzle new blockIterating: (1 to: 5). >> >> block value: [:p | Transcript show: p; cr] value: [:p | p < 3] >> >> >> >> But the above fails with a 'Block cannot return' (that's the [^ self] block >> in the #blockIterating: method). I have attached the code; just do "Puzzle >> new fail". >> >> I can't find a workaround. >> >> How should I proceed to get a working iterator block ? >> >> Stef >> > > > > |
Hmm, now I see that my last idea does not work if I want to reuse the
block... | block | block := (Puzzle new blockIterating: (1 to: 5)) value: [ ^self]. block value: [:p | Transcript show: p; cr] value: [:p | p < 3]. block value: [:p | Transcript show: p; cr] value: [:p | p < 4]. This stops after 1 2 while your implementation works fine: the Transcript shows 1 2 1 2 3 indeed. Stef > Ah, so that's the bottom line. Thanks a lot ! I'm now trying to wrap my > head around these concepts... > > It looks like I could also get the return block as an outside argument, > like this: > > > Puzzle>>blockIterating: aCollection > > ^ [:returnBlock | > [:doBlock :whileBlock | > aCollection do: [:i | > (whileBlock value: i) ifFalse: returnBlock. > doBlock value: i]]]. > > > and then use it as follow: > > > | block | > > block := (Puzzle new blockIterating: (1 to: 5)) value: [ ^self]. > > block value: [:p | Transcript show: p; cr] value: [:p | p < 3] > > > A bit hard to read but it seems to work. Does that make sense ? |
In reply to this post by Stéphane Rollandin
Dnia 14.05.2015 17:33:45, Stéphane Rollandin napisał(a):
> Hello all, > > I'm finding myself unable to implement something seemingly simple. > Here is the puzzle: > > I want an iterator factory which, given a specific collection, > produces a block taking two arguments, also blocks. One (the doBlock) > tells what should be done for each element of the collection, and the > other (the whileBlock) is a test allowing to abort the whole > operation. > > So, something like this: > > Puzzle>>blockIterating: aCollection > > ^ [:doBlock :whileBlock | > aCollection do: [:i | > (whileBlock value: i) ifFalse: [^ self]. > doBlock value: i]]. > > > Then I could do things like the following: > > > | block | > > block := Puzzle new blockIterating: (1 to: 5). > > block value: [:p | Transcript show: p; cr] value: [:p | p < 3] > > > > But the above fails with a 'Block cannot return' (that's the [^ self] > block in the #blockIterating: method). I have attached the code; just > do "Puzzle new fail". > > I can't find a workaround. > > How should I proceed to get a working iterator block ? > > Stef The behaviour is correct. The ^ operator has lexical scope. It returns from the lexically enclosing method. In this case it would return from blockIterating:, but at this point the method has already returned, so you cannot return from it the second time. What you really want is another method. Just use two separate methods: for: aCollection do: aDoBlock unless: aWhileBlock aCollection do: [:i | (whileBlock value: i) ifFalse: [^ self]. doBlock value: i ] blockIterating: aCollection [:doBlock :whileBlock | self for: aCollection do: doBlock while: whileBlock ]. |
Oh, sorry for the duplicate answer. I'm tired :-(
|
Administrator
|
In reply to this post by Stéphane Rollandin
I would strongly advise against this. Firstly, exceptions are conceptually a way to indicate a condition that is... exceptional! So in that sense it's confusing. But maybe more importantly, I've encountered several areas in Pharo where exceptions were hijacked in this way and it led to lots of pain. The one that comes to mind first is the mechanism to show progress during a long operation. Because exceptions were used inappropriately, only one interested party would be notified, and then they would swallow the exception. The appropriate technique in that case was an observer pattern, now implemented with announcements. I encourage you to find a corresponding pattern!
Cheers,
Sean |
Free forum by Nabble | Edit this page |