A little puzzle

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

A little puzzle

Stéphane Rollandin
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
Reply | Threaded
Open this post in threaded view
|

A little puzzle

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


Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

Balázs Kósi-2
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


Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

Stéphane Rollandin
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

Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

marcel.taeumel
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
Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

Levente Uzonyi-2
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
>

Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

Stéphane Rollandin
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
>>
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

Stéphane Rollandin
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 ?



Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

Mateusz Grotek
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
        ].



Reply | Threaded
Open this post in threaded view
|

Re: [SPAM] Re: [squeak-dev] A little puzzle

Mateusz Grotek
Oh, sorry for the duplicate answer. I'm tired :-(

Reply | Threaded
Open this post in threaded view
|

Re: A little puzzle

Sean P. DeNigris
Administrator
In reply to this post by Stéphane Rollandin
Stéphane Rollandin wrote
But actually, I think I just found a workaround: throw an exception,
such as Abort which by the way seems to be unused.
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