Escaping from loops in scripts

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

Escaping from loops in scripts

Nicolas Cellier
Hi all,
when writing scripts, I often need the ability to interrupt a loop.
This is the traditional break instruction of C/Python or more advanced exit of FORTRAN/ADA (we can tell from which nexted loop we exit).

I could use exceptions, but then handling exit from nested loops is tricky (see below).
One possible way to do it in Smalltalk is to use blocks, and interrupt with non local return.

Object>>escape: aBlock
    "Give the ability to exit execution of aBlock by returning control to sender.
    aBlock will take an argument, which is a door for returning control.
    This is useful for breaking a loop, for example:
     self escape: [:exit | someCollection do: [:e | e fullfilSomeCondition ifTrue: [exit value]. e doSometing]]"
    aBlock value: [^self]

| sum |
sum := 0.
self escape: [:exitOuterLoop | someCollection do: [:e |
    self escape: [:exitInnerLoop | someOtherCollection do: [:e2 |
        e2 > e ifTrue: [exitOuterLoop value].
        e2 = e ifTrue: [exitInnerLoop value].
        sum := sum + e]]]].
^sum

We can also use the escape: inside the loop to avoid chained ifTrue:ifFalse:
but it's then convenient to let escape: return a value:

Object>>escape: aBlock
    ^aBlock value: [:result | ^result]

aCollection collect: [:e |
    e escape: [:return |
        e < 0 ifTrue: [return value: e].
        e > 20 ifTrue: [return value: 401 ln].
       (e squared + 1) ln]]


At that time, I found that amusing, now I find it useful.
I don't see such support in trunk image, could we add it?
Do you think of a better name?
(not setjmp: please)

Unless you come with better alternatives... One thing that is questionable is that the receiver of escape: is void (escape: is a utility).

It start looking like FORTRAN/ADA exit instruction with explicit naming of loop but I don't like it better.

Maybe sending the message to the block itself sounds less arbitrary.

BlockClosure>>handleExit
    ^self value: [:result | ^result]

| sum |
sum := 0.
[:exitOuterLoop | someCollection do: [:e |
     [:exitInnerLoop | someOtherCollection do: [:e2 |
        e2 > e ifTrue: [exitOuterLoop value: nil].
        e2 = e ifTrue: [exitInnerLoop value: nil].
        sum := sum + e]] handleExit ]] handleExit.
^sum

Though, I don't find such post-fixing satisfactory: exitOuterLoop and handleExit are too far from each other... Especially in scripts that tend to be longer than ordinary methods (we don't want to factor every quick and dirty procedure into proper classes/methods when there is no reuse objective or when they are too specific).


Reply | Threaded
Open this post in threaded view
|

Re: Escaping from loops in scripts

Tobias Pape
Hi Nicolas,


> On 09.12.2018, at 11:37, Nicolas Cellier <[hidden email]> wrote:
>
> Hi all,
> when writing scripts, I often need the ability to interrupt a loop.
> This is the traditional break instruction of C/Python or more advanced exit of FORTRAN/ADA (we can tell from which nexted loop we exit).
>
> I could use exceptions, but then handling exit from nested loops is tricky (see below).
> One possible way to do it in Smalltalk is to use blocks, and interrupt with non local return.
>


My way would be to use at most one method per loop and have an exit block then, like so


A>>run: x
  |y z |
  y = 0.
  z := x
  [z < 10] whileTrue:
     [y := self foobar: z ifExit: [ ^y].
      z := z + 1]
  ^ y

A>> foobar: x ifExit: aBlock

  | y |
  y := x.
  [y isOdd] whileTrue:
    [y := y + 2.
    " somehting something"
    y > 3 ifTrue: aBlock]
  ^ y

Best regards
        -Tobias

> ref http://lists.gnu.org/archive/html/help-smalltalk/2008-06/msg00077.html
>
> Object>>escape: aBlock
>     "Give the ability to exit execution of aBlock by returning control to sender.
>     aBlock will take an argument, which is a door for returning control.
>     This is useful for breaking a loop, for example:
>      self escape: [:exit | someCollection do: [:e | e fullfilSomeCondition ifTrue: [exit value]. e doSometing]]"
>     aBlock value: [^self]
>
> | sum |
> sum := 0.
> self escape: [:exitOuterLoop | someCollection do: [:e |
>     self escape: [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value].
>         e2 = e ifTrue: [exitInnerLoop value].
>         sum := sum + e]]]].
> ^sum
>
> We can also use the escape: inside the loop to avoid chained ifTrue:ifFalse:
> but it's then convenient to let escape: return a value:
>
> Object>>escape: aBlock
>     ^aBlock value: [:result | ^result]
>
> aCollection collect: [:e |
>     e escape: [:return |
>         e < 0 ifTrue: [return value: e].
>         e > 20 ifTrue: [return value: 401 ln].
>        (e squared + 1) ln]]
>
> ref https://stackoverflow.com/questions/7547750/smalltalk-block-can-i-explicitly-set-the-returning-value-and-stop-executing-th/11532045#11532045
>
> At that time, I found that amusing, now I find it useful.
> I don't see such support in trunk image, could we add it?
> Do you think of a better name?
> (not setjmp: please)
>
> Unless you come with better alternatives... One thing that is questionable is that the receiver of escape: is void (escape: is a utility).
>
> There is https://stackoverflow.com/questions/52683795/gnu-smalltalk-break-from-whiletrue-loop-without-return/52702174#52702174 
> It start looking like FORTRAN/ADA exit instruction with explicit naming of loop but I don't like it better.
>
> Maybe sending the message to the block itself sounds less arbitrary.
>
> BlockClosure>>handleExit
>     ^self value: [:result | ^result]
>
> | sum |
> sum := 0.
> [:exitOuterLoop | someCollection do: [:e |
>      [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value: nil].
>         e2 = e ifTrue: [exitInnerLoop value: nil].
>         sum := sum + e]] handleExit ]] handleExit.
> ^sum
>
> Though, I don't find such post-fixing satisfactory: exitOuterLoop and handleExit are too far from each other... Especially in scripts that tend to be longer than ordinary methods (we don't want to factor every quick and dirty procedure into proper classes/methods when there is no reuse objective or when they are too specific).
>


Reply | Threaded
Open this post in threaded view
|

Re: Escaping from loops in scripts

Nicolas Cellier
Hi Tobias,
I don't want to pay the burden of making own class/methods, I just want to write a dumb and dirty script.

But to answer to myself, what I asked is already in the image:

BlockClosure>>valueWithExit
      self value: [ ^nil ]

It would be more convenient with a return value, but for now it's ok.

Le dim. 9 déc. 2018 à 14:08, Tobias Pape <[hidden email]> a écrit :
Hi Nicolas,


> On 09.12.2018, at 11:37, Nicolas Cellier <[hidden email]> wrote:
>
> Hi all,
> when writing scripts, I often need the ability to interrupt a loop.
> This is the traditional break instruction of C/Python or more advanced exit of FORTRAN/ADA (we can tell from which nexted loop we exit).
>
> I could use exceptions, but then handling exit from nested loops is tricky (see below).
> One possible way to do it in Smalltalk is to use blocks, and interrupt with non local return.
>


My way would be to use at most one method per loop and have an exit block then, like so


A>>run: x
  |y z |
  y = 0.
  z := x
  [z < 10] whileTrue:
     [y := self foobar: z ifExit: [ ^y].
      z := z + 1]
  ^ y

A>> foobar: x ifExit: aBlock

  | y |
  y := x.
  [y isOdd] whileTrue:
    [y := y + 2.
    " somehting something"
    y > 3 ifTrue: aBlock]
  ^ y

Best regards
        -Tobias

> ref http://lists.gnu.org/archive/html/help-smalltalk/2008-06/msg00077.html
>
> Object>>escape: aBlock
>     "Give the ability to exit execution of aBlock by returning control to sender.
>     aBlock will take an argument, which is a door for returning control.
>     This is useful for breaking a loop, for example:
>      self escape: [:exit | someCollection do: [:e | e fullfilSomeCondition ifTrue: [exit value]. e doSometing]]"
>     aBlock value: [^self]
>
> | sum |
> sum := 0.
> self escape: [:exitOuterLoop | someCollection do: [:e |
>     self escape: [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value].
>         e2 = e ifTrue: [exitInnerLoop value].
>         sum := sum + e]]]].
> ^sum
>
> We can also use the escape: inside the loop to avoid chained ifTrue:ifFalse:
> but it's then convenient to let escape: return a value:
>
> Object>>escape: aBlock
>     ^aBlock value: [:result | ^result]
>
> aCollection collect: [:e |
>     e escape: [:return |
>         e < 0 ifTrue: [return value: e].
>         e > 20 ifTrue: [return value: 401 ln].
>        (e squared + 1) ln]]
>
> ref https://stackoverflow.com/questions/7547750/smalltalk-block-can-i-explicitly-set-the-returning-value-and-stop-executing-th/11532045#11532045
>
> At that time, I found that amusing, now I find it useful.
> I don't see such support in trunk image, could we add it?
> Do you think of a better name?
> (not setjmp: please)
>
> Unless you come with better alternatives... One thing that is questionable is that the receiver of escape: is void (escape: is a utility).
>
> There is https://stackoverflow.com/questions/52683795/gnu-smalltalk-break-from-whiletrue-loop-without-return/52702174#52702174
> It start looking like FORTRAN/ADA exit instruction with explicit naming of loop but I don't like it better.
>
> Maybe sending the message to the block itself sounds less arbitrary.
>
> BlockClosure>>handleExit
>     ^self value: [:result | ^result]
>
> | sum |
> sum := 0.
> [:exitOuterLoop | someCollection do: [:e |
>      [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value: nil].
>         e2 = e ifTrue: [exitInnerLoop value: nil].
>         sum := sum + e]] handleExit ]] handleExit.
> ^sum
>
> Though, I don't find such post-fixing satisfactory: exitOuterLoop and handleExit are too far from each other... Especially in scripts that tend to be longer than ordinary methods (we don't want to factor every quick and dirty procedure into proper classes/methods when there is no reuse objective or when they are too specific).
>




Reply | Threaded
Open this post in threaded view
|

Re: Escaping from loops in scripts

Chris Muller-3
In reply to this post by Nicolas Cellier
On Sun, Dec 9, 2018 at 4:37 AM Nicolas Cellier
<[hidden email]> wrote:
>
> Hi all,
> when writing scripts, I often need the ability to interrupt a loop.

I use #detect:ifFound:ifNone: for such cases.

> This is the traditional break instruction of C/Python or more advanced exit of FORTRAN/ADA (we can tell from which nexted loop we exit).
>
> I could use exceptions, but then handling exit from nested loops is tricky (see below).
> One possible way to do it in Smalltalk is to use blocks, and interrupt with non local return.

I find nested #detect:ifFound:ifNone:'s structurally elegant enough,
that the "detect" nomenclature doesn't bother me.

> ref http://lists.gnu.org/archive/html/help-smalltalk/2008-06/msg00077.html
>
> Object>>escape: aBlock
>     "Give the ability to exit execution of aBlock by returning control to sender.
>     aBlock will take an argument, which is a door for returning control.
>     This is useful for breaking a loop, for example:
>      self escape: [:exit | someCollection do: [:e | e fullfilSomeCondition ifTrue: [exit value]. e doSometing]]"
>     aBlock value: [^self]
>
> | sum |
> sum := 0.
> self escape: [:exitOuterLoop | someCollection do: [:e |
>     self escape: [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value].
>         e2 = e ifTrue: [exitInnerLoop value].
>         sum := sum + e]]]].
> ^sum

You want to do non-local returns to outer blocks within the same
method?  Yikes, I love spaghetti as much as anyone, but only to eat!
Isn't a hierarchical composition of blocks, as conveyed by modern
tile-based coding tools like Scratch and Etoys, tremendously easier to
follow?  Non-local returns make a method procedural by nature,
basically like sprinkling "goto"s into your code.

> We can also use the escape: inside the loop to avoid chained ifTrue:ifFalse:
> but it's then convenient to let escape: return a value:
>
> Object>>escape: aBlock
>     ^aBlock value: [:result | ^result]
>
> aCollection collect: [:e |
>     e escape: [:return |
>         e < 0 ifTrue: [return value: e].
>         e > 20 ifTrue: [return value: 401 ln].
>        (e squared + 1) ln]]

OMG, LOL!!  Arrgghh!  Please, just give me the "chained ifTrue:ifFalse:"!!!

The closest I've ever gotten to this is to separate error-handling
code from processing code, to make it more readable, thus:

   | error |  error := [ ^ MyError signal: 'User not found' ].
   ^ users at: requestedId ifAbsent: error

The return only even being needed because Squeak does not support
non-resumable Exceptions.

> ref https://stackoverflow.com/questions/7547750/smalltalk-block-can-i-explicitly-set-the-returning-value-and-stop-executing-th/11532045#11532045
>
> At that time, I found that amusing, now I find it useful.

No way, I don't believe it...   :/

> I don't see such support in trunk image, could we add it?
> Do you think of a better name?
> (not setjmp: please)

If such an albatross must go in trunk, setjmp: would be good.

> Unless you come with better alternatives...

Whole composed expressions!

> One thing that is questionable is that the receiver of escape: is void (escape: is a utility).
>
> There is https://stackoverflow.com/questions/52683795/gnu-smalltalk-break-from-whiletrue-loop-without-return/52702174#52702174

It's "cool" for showing how terse the design of Smalltalk language and
environment, but not something actually recommended to do I hope,
since it simply makes the language more complex unnecessarily.

My 2 cents.

 - Chris

> It start looking like FORTRAN/ADA exit instruction with explicit naming of loop but I don't like it better.
>
> Maybe sending the message to the block itself sounds less arbitrary.
>
> BlockClosure>>handleExit
>     ^self value: [:result | ^result]
>
> | sum |
> sum := 0.
> [:exitOuterLoop | someCollection do: [:e |
>      [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value: nil].
>         e2 = e ifTrue: [exitInnerLoop value: nil].
>         sum := sum + e]] handleExit ]] handleExit.
> ^sum
>
> Though, I don't find such post-fixing satisfactory: exitOuterLoop and handleExit are too far from each other... Especially in scripts that tend to be longer than ordinary methods (we don't want to factor every quick and dirty procedure into proper classes/methods when there is no reuse objective or when they are too specific).
>

Reply | Threaded
Open this post in threaded view
|

Re: Escaping from loops in scripts

Nicolas Cellier
Hi Chris,
all I ask is a one liner method like this:

BlockClosure>>handleExit
    ^self value: [:result | ^result]

The usage I'm doing is my own business.
It is indeed very procedural, and plenty of goto like because it's just a dumb piece of code for generating smoke tests for fdlibm.
If you want to know, I iterate other set of bits that I compose into Float with basicAt:put:
It does not need to be object oriented, nor to be nice, just efficient.
And I don't want to be clever when I don't need to!
Lazyness rules ;)

Le dim. 9 déc. 2018 à 21:32, Chris Muller <[hidden email]> a écrit :
On Sun, Dec 9, 2018 at 4:37 AM Nicolas Cellier
<[hidden email]> wrote:
>
> Hi all,
> when writing scripts, I often need the ability to interrupt a loop.

I use #detect:ifFound:ifNone: for such cases.

> This is the traditional break instruction of C/Python or more advanced exit of FORTRAN/ADA (we can tell from which nexted loop we exit).
>
> I could use exceptions, but then handling exit from nested loops is tricky (see below).
> One possible way to do it in Smalltalk is to use blocks, and interrupt with non local return.

I find nested #detect:ifFound:ifNone:'s structurally elegant enough,
that the "detect" nomenclature doesn't bother me.

> ref http://lists.gnu.org/archive/html/help-smalltalk/2008-06/msg00077.html
>
> Object>>escape: aBlock
>     "Give the ability to exit execution of aBlock by returning control to sender.
>     aBlock will take an argument, which is a door for returning control.
>     This is useful for breaking a loop, for example:
>      self escape: [:exit | someCollection do: [:e | e fullfilSomeCondition ifTrue: [exit value]. e doSometing]]"
>     aBlock value: [^self]
>
> | sum |
> sum := 0.
> self escape: [:exitOuterLoop | someCollection do: [:e |
>     self escape: [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value].
>         e2 = e ifTrue: [exitInnerLoop value].
>         sum := sum + e]]]].
> ^sum

You want to do non-local returns to outer blocks within the same
method?  Yikes, I love spaghetti as much as anyone, but only to eat!
Isn't a hierarchical composition of blocks, as conveyed by modern
tile-based coding tools like Scratch and Etoys, tremendously easier to
follow?  Non-local returns make a method procedural by nature,
basically like sprinkling "goto"s into your code.

> We can also use the escape: inside the loop to avoid chained ifTrue:ifFalse:
> but it's then convenient to let escape: return a value:
>
> Object>>escape: aBlock
>     ^aBlock value: [:result | ^result]
>
> aCollection collect: [:e |
>     e escape: [:return |
>         e < 0 ifTrue: [return value: e].
>         e > 20 ifTrue: [return value: 401 ln].
>        (e squared + 1) ln]]

OMG, LOL!!  Arrgghh!  Please, just give me the "chained ifTrue:ifFalse:"!!!

The closest I've ever gotten to this is to separate error-handling
code from processing code, to make it more readable, thus:

   | error |  error := [ ^ MyError signal: 'User not found' ].
   ^ users at: requestedId ifAbsent: error

The return only even being needed because Squeak does not support
non-resumable Exceptions.

> ref https://stackoverflow.com/questions/7547750/smalltalk-block-can-i-explicitly-set-the-returning-value-and-stop-executing-th/11532045#11532045
>
> At that time, I found that amusing, now I find it useful.

No way, I don't believe it...   :/

> I don't see such support in trunk image, could we add it?
> Do you think of a better name?
> (not setjmp: please)

If such an albatross must go in trunk, setjmp: would be good.

> Unless you come with better alternatives...

Whole composed expressions!

> One thing that is questionable is that the receiver of escape: is void (escape: is a utility).
>
> There is https://stackoverflow.com/questions/52683795/gnu-smalltalk-break-from-whiletrue-loop-without-return/52702174#52702174

It's "cool" for showing how terse the design of Smalltalk language and
environment, but not something actually recommended to do I hope,
since it simply makes the language more complex unnecessarily.

My 2 cents.

 - Chris

> It start looking like FORTRAN/ADA exit instruction with explicit naming of loop but I don't like it better.
>
> Maybe sending the message to the block itself sounds less arbitrary.
>
> BlockClosure>>handleExit
>     ^self value: [:result | ^result]
>
> | sum |
> sum := 0.
> [:exitOuterLoop | someCollection do: [:e |
>      [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value: nil].
>         e2 = e ifTrue: [exitInnerLoop value: nil].
>         sum := sum + e]] handleExit ]] handleExit.
> ^sum
>
> Though, I don't find such post-fixing satisfactory: exitOuterLoop and handleExit are too far from each other... Especially in scripts that tend to be longer than ordinary methods (we don't want to factor every quick and dirty procedure into proper classes/methods when there is no reuse objective or when they are too specific).
>



Reply | Threaded
Open this post in threaded view
|

Re: Escaping from loops in scripts

Tobias Pape
In reply to this post by Nicolas Cellier

> On 09.12.2018, at 21:22, Nicolas Cellier <[hidden email]> wrote:
>
> Hi Tobias,
> I don't want to pay the burden of making own class/methods, I just want to write a dumb and dirty script.

Makes sense…
I probably would do nested blocks, but that's equally inelegant.

>
> But to answer to myself, what I asked is already in the image:
>
> BlockClosure>>valueWithExit
>       self value: [ ^nil ]
>
> It would be more convenient with a return value, but for now it's ok.

That's a very interesting thing find, thank you!
:)

Best regards
        -Tobias

>
> Le dim. 9 déc. 2018 à 14:08, Tobias Pape <[hidden email]> a écrit :
> Hi Nicolas,
>
>
> > On 09.12.2018, at 11:37, Nicolas Cellier <[hidden email]> wrote:
> >
> > Hi all,
> > when writing scripts, I often need the ability to interrupt a loop.
> > This is the traditional break instruction of C/Python or more advanced exit of FORTRAN/ADA (we can tell from which nexted loop we exit).
> >
> > I could use exceptions, but then handling exit from nested loops is tricky (see below).
> > One possible way to do it in Smalltalk is to use blocks, and interrupt with non local return.
> >
>
>
> My way would be to use at most one method per loop and have an exit block then, like so
>
>
> A>>run: x
>   |y z |
>   y = 0.
>   z := x
>   [z < 10] whileTrue:
>      [y := self foobar: z ifExit: [ ^y].
>       z := z + 1]
>   ^ y
>
> A>> foobar: x ifExit: aBlock
>
>   | y |
>   y := x.
>   [y isOdd] whileTrue:
>     [y := y + 2.
>     " somehting something"
>     y > 3 ifTrue: aBlock]
>   ^ y
>
> Best regards
>         -Tobias
>
> > ref http://lists.gnu.org/archive/html/help-smalltalk/2008-06/msg00077.html
> >
> > Object>>escape: aBlock
> >     "Give the ability to exit execution of aBlock by returning control to sender.
> >     aBlock will take an argument, which is a door for returning control.
> >     This is useful for breaking a loop, for example:
> >      self escape: [:exit | someCollection do: [:e | e fullfilSomeCondition ifTrue: [exit value]. e doSometing]]"
> >     aBlock value: [^self]
> >
> > | sum |
> > sum := 0.
> > self escape: [:exitOuterLoop | someCollection do: [:e |
> >     self escape: [:exitInnerLoop | someOtherCollection do: [:e2 |
> >         e2 > e ifTrue: [exitOuterLoop value].
> >         e2 = e ifTrue: [exitInnerLoop value].
> >         sum := sum + e]]]].
> > ^sum
> >
> > We can also use the escape: inside the loop to avoid chained ifTrue:ifFalse:
> > but it's then convenient to let escape: return a value:
> >
> > Object>>escape: aBlock
> >     ^aBlock value: [:result | ^result]
> >
> > aCollection collect: [:e |
> >     e escape: [:return |
> >         e < 0 ifTrue: [return value: e].
> >         e > 20 ifTrue: [return value: 401 ln].
> >        (e squared + 1) ln]]
> >
> > ref https://stackoverflow.com/questions/7547750/smalltalk-block-can-i-explicitly-set-the-returning-value-and-stop-executing-th/11532045#11532045
> >
> > At that time, I found that amusing, now I find it useful.
> > I don't see such support in trunk image, could we add it?
> > Do you think of a better name?
> > (not setjmp: please)
> >
> > Unless you come with better alternatives... One thing that is questionable is that the receiver of escape: is void (escape: is a utility).
> >
> > There is https://stackoverflow.com/questions/52683795/gnu-smalltalk-break-from-whiletrue-loop-without-return/52702174#52702174 
> > It start looking like FORTRAN/ADA exit instruction with explicit naming of loop but I don't like it better.
> >
> > Maybe sending the message to the block itself sounds less arbitrary.
> >
> > BlockClosure>>handleExit
> >     ^self value: [:result | ^result]
> >
> > | sum |
> > sum := 0.
> > [:exitOuterLoop | someCollection do: [:e |
> >      [:exitInnerLoop | someOtherCollection do: [:e2 |
> >         e2 > e ifTrue: [exitOuterLoop value: nil].
> >         e2 = e ifTrue: [exitInnerLoop value: nil].
> >         sum := sum + e]] handleExit ]] handleExit.
> > ^sum
> >
> > Though, I don't find such post-fixing satisfactory: exitOuterLoop and handleExit are too far from each other... Especially in scripts that tend to be longer than ordinary methods (we don't want to factor every quick and dirty procedure into proper classes/methods when there is no reuse objective or when they are too specific).
> >
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Escaping from loops in scripts

Eliot Miranda-2
In reply to this post by Nicolas Cellier
Hi Nicolas,

On Dec 9, 2018, at 12:22 PM, Nicolas Cellier <[hidden email]> wrote:

Hi Tobias,
I don't want to pay the burden of making own class/methods, I just want to write a dumb and dirty script.

But to answer to myself, what I asked is already in the image:

BlockClosure>>valueWithExit
      self value: [ ^nil ]

It would be more convenient with a return value, but for now it's ok.

So add

BlockClosure>>valueWithExitValue
      self value: [:result| ^result ]


Le dim. 9 déc. 2018 à 14:08, Tobias Pape <[hidden email]> a écrit :
Hi Nicolas,


> On 09.12.2018, at 11:37, Nicolas Cellier <[hidden email]> wrote:
>
> Hi all,
> when writing scripts, I often need the ability to interrupt a loop.
> This is the traditional break instruction of C/Python or more advanced exit of FORTRAN/ADA (we can tell from which nexted loop we exit).
>
> I could use exceptions, but then handling exit from nested loops is tricky (see below).
> One possible way to do it in Smalltalk is to use blocks, and interrupt with non local return.
>


My way would be to use at most one method per loop and have an exit block then, like so


A>>run: x
  |y z |
  y = 0.
  z := x
  [z < 10] whileTrue:
     [y := self foobar: z ifExit: [ ^y].
      z := z + 1]
  ^ y

A>> foobar: x ifExit: aBlock

  | y |
  y := x.
  [y isOdd] whileTrue:
    [y := y + 2.
    " somehting something"
    y > 3 ifTrue: aBlock]
  ^ y

Best regards
        -Tobias

> ref http://lists.gnu.org/archive/html/help-smalltalk/2008-06/msg00077.html
>
> Object>>escape: aBlock
>     "Give the ability to exit execution of aBlock by returning control to sender.
>     aBlock will take an argument, which is a door for returning control.
>     This is useful for breaking a loop, for example:
>      self escape: [:exit | someCollection do: [:e | e fullfilSomeCondition ifTrue: [exit value]. e doSometing]]"
>     aBlock value: [^self]
>
> | sum |
> sum := 0.
> self escape: [:exitOuterLoop | someCollection do: [:e |
>     self escape: [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value].
>         e2 = e ifTrue: [exitInnerLoop value].
>         sum := sum + e]]]].
> ^sum
>
> We can also use the escape: inside the loop to avoid chained ifTrue:ifFalse:
> but it's then convenient to let escape: return a value:
>
> Object>>escape: aBlock
>     ^aBlock value: [:result | ^result]
>
> aCollection collect: [:e |
>     e escape: [:return |
>         e < 0 ifTrue: [return value: e].
>         e > 20 ifTrue: [return value: 401 ln].
>        (e squared + 1) ln]]
>
> ref https://stackoverflow.com/questions/7547750/smalltalk-block-can-i-explicitly-set-the-returning-value-and-stop-executing-th/11532045#11532045
>
> At that time, I found that amusing, now I find it useful.
> I don't see such support in trunk image, could we add it?
> Do you think of a better name?
> (not setjmp: please)
>
> Unless you come with better alternatives... One thing that is questionable is that the receiver of escape: is void (escape: is a utility).
>
> There is https://stackoverflow.com/questions/52683795/gnu-smalltalk-break-from-whiletrue-loop-without-return/52702174#52702174
> It start looking like FORTRAN/ADA exit instruction with explicit naming of loop but I don't like it better.
>
> Maybe sending the message to the block itself sounds less arbitrary.
>
> BlockClosure>>handleExit
>     ^self value: [:result | ^result]
>
> | sum |
> sum := 0.
> [:exitOuterLoop | someCollection do: [:e |
>      [:exitInnerLoop | someOtherCollection do: [:e2 |
>         e2 > e ifTrue: [exitOuterLoop value: nil].
>         e2 = e ifTrue: [exitInnerLoop value: nil].
>         sum := sum + e]] handleExit ]] handleExit.
> ^sum
>
> Though, I don't find such post-fixing satisfactory: exitOuterLoop and handleExit are too far from each other... Especially in scripts that tend to be longer than ordinary methods (we don't want to factor every quick and dirty procedure into proper classes/methods when there is no reuse objective or when they are too specific).
>





Reply | Threaded
Open this post in threaded view
|

Re: Escaping from loops in scripts

alistairgrant
A bit late to the party, but the following all contain good
suggestions (with some repetition of what's been written here):

* http://forum.world.st/smalltalk-breaking-out-of-a-loop-td3030926.html
* https://groups.google.com/forum/#!topic/comp.lang.smalltalk/dwR21ILLR2M
* https://www.lukas-renggli.ch/blog/continue-break

HTH,
Alistair