Localizing up-arrow

Sheldon Nicholl sheldonn at teleport.com
Sat Oct 17 04:03:44 UTC 1998

At 09:04 PM 10/12/98 -0700, Sheldon Nicholl wrote:
>>I really feel like I'm going out on a limb by saying this, but I think
>>Smalltalk would be a better language if non-local returns were
>>removed entirely. They cause trouble all across the board, from
>>the Debugger to the VM to beginning Smalltalkers trying to learn
>>the language.

>Interesting idea, but could you get away with it?

I think so. First, let me clarify my proposal: redefine the meaning
of ^ to be a local return (thisContext sender), not a non-local return
(thisContext home sender), and use continuations for non-local stuff.
I consider the use of exceptions here (or similar mechanisms) a distant
dark-horse alternative, to be used only if no one wants to use
continuations. That is, exceptions should be used for exceptions, not
to get non-local returns.

>For example, would the following be illegal

>stream atEnd ifTrue: [^self] ...

>1 to: 3 do: [:x | (self foo: x) ifTrue: [^self] ...]

>[x < 4] whileTrue: [(self foo: x) ifTrue: [^self] ...]

I'll be a bit slippery here and give four alternative proposals,
ranked in increasing order of preferability:

(1) The Status-Quo Solution
(2) The Language Realignment Solution
(3) The Clean Programming Solution
(4) The Continuation Solution

(1) The Status-Quo Solution: Since all of them use constructs like
whileTrue:, ifTrue:, and so on which are always inlined, the
reinterpretation of ^ as a local return will work since these
constructs don't create a context. Not seriously recommended.

(2) The Language Realignment Solution: We admit that the automatic
inlining of these constructs (ifTrue:, etc.) by the compiler
constitues the legislation of them as built-in constructs like
other languages, such as Pascal (if-then, etc.). No message is
sent, no context is created, they can't be browsed on, can't
be traced, the code in their methods is not executed, can't be changed,
and so on. Just like language constructs.

The outcome of this is to realign the language by creating special
constructs for them which are recognized by the Compiler. That
is, we only change the front-end syntax; what comes out the end
in bytecode will hardly change, if at all. Once they become special
constructs, blocks are unnecessary, so the reinterpretation of ^
as a local return works naturally.

(3) The Clean Programming Solution: I feel like I'm being a bit
unfair here, since most of the examples given above seem more like 
theoretical expressions than examples of actual use, but I'll take 
them as they stand.

The examples also allow me to add to the list of troubles caused
by non-local returns: "questionable" style.

>stream atEnd ifTrue: [^self]

Why not just say:

    stream atEnd ifFalse: [...the rest of the method...]

or maybe better:

    stream atEnd ifFalse: [self doTheRest]

I think I can answer this question: because it looks terrible.
I'll admit I've used it countless times, but almost always with
a twinge of conscience.

But we can't let imperfections in the syntax of the language
push us into a questionable programming practice.

>1 to: 3 do: [:x | (self foo: x) ifTrue: [^self] ...]

My gut reaction here would be to try using a detect: e.g.,

(1 to: 3) detect: [:i | self foo: x ...]

I can't think of a time over the last couple of years (at least)
when I've had to use anything but a detect: to get the effect of 
jumping out of a loop prematurely. And detect: can be rewritten to 
avoid the non-local return. Or if you want something more general,
how about something like 

aCollection do: iterationBlock until: exitBlock

which does the iterationBlock until exitBlock becomes true.

>[x < 4] whileTrue: [(self foo: x) ifTrue: [^self] ...]

This one is a bit bizarre, since I can't think of an example in the
image where one needs to jump out of the body of a while loop. My
feeling is that the receiver of whileTrue: should be powerful enough
to detect any escape conditions. So why not move (self foo: x) into
the receiver block? 

(4) The Continuation Solution. This is the original proposal, to
use continuations for non-local movements. So in those rare cases
when it really is appropriate to jump further than one's sender,
the machinery is there, and it's clean every time.



Getting back to Tim Rowledge's original comment:
>Unwind support is needed to handle the case of a non-local return merrily
>skipping past your unwind protection block, leaving your files open,
>sockets flailing or other catastrophes imminent.
>A non-local return can jump from one context back up to another without
>passing Go, thereby causing untold trouble. The VM mod would scan up the
>stack from source to target, checking for marked contexts on the way; any
>marked context found woud be sent a suitable message, somewhat like

I just want to plead that in case something like this is adopted, that
it be done at the image level. It involves far too many operations, way
too much intelligence for something that should be in the VM, which 
should consist of simple clean primitives only. Unwinding is something
that a programmer may want to modify, which is another argument for
putting it in the image.

More information about the Squeak-dev mailing list