[net.lang.st80] id AA09957; Mon, 6 Feb 84 14:27:05 est

Pavel.pa@PARC-MAXC.ARPA@cornell.UUCP (Pavel.pa@PARC-MAXC.ARPA) (02/06/84)

Message-Id: <8402061924.AA00258@arpa.CORNELL>
Received: from PARC-MAXC.ARPA by arpa.CORNELL (4.12/3.14)
	id AA00258; Mon, 6 Feb 84 14:24:56 est
Date: 6 Feb 84 11:22:52 PST
From: Pavel.pa@PARC-MAXC.ARPA
Subject: Re: Response to Blocks Question
To: net-lang-st80@CORNELL

My, my, what an interesting piece of code...

I was fairly certain that this was illegal (and should therefore bomb),
so I tried it out.  I did the following:

	Add a method to class UndefinedObject:
		strange
			^[^0]

	In a workspace, execute the following code:
		| i j |
		i <- nil strange.
		j <- i value

As I hoped, the system threw up its hands, though they weren't quite the
hands I was expecting.  I got a notifier with the error "Message not
understood: cannotReturn:".  The message "cannotReturn:" was sent to a
SmallInteger somewhere, presumably from inside the interpreter.  In any
case, the ultimate reason for the error is fairly easy to understand.
Let me explain it in two ways, firstly from the philosophical point of
view and then operationally.

Philosopically: It is illegal to return from a context more than once.
If you think about it, it really doesn't make any sense, does it?  That
means returning along a chain of procedure calls, essentially going back
in time but with a perhaps radically changed state.  How could you
reason about such a program?  The very same message send returns two
values \at/ \the/ \same/ \time/?  Pretty strange.  The down-to-earth
effect of this is that **it is illegal to evaluate a "returning" block
after its home context has already returned once**.  (A "returning"
block is one whose final statement begins with "^".)

Operationally:  Here is what happened:
	1) When "strange" was called, the "home" pointer in the block context
was set to the current context, this invocation of "strange".
	2) When "strange" returned, the method context for "strange" had its
"sender" pointer reset to "nil", indicating that this context had
already been returned from.
	3) When the block was evaluated, it pushed 0 on the stack and attempted
to return to the "sender" of the "home" of the block, now equal to
"nil".  This caused an error and the interpreter presumably initiated a
send of "cannotReturn:" to whatever was on the top of the stack, in this
case a SmallInteger.

It is important to distinguish in your mind the block "[0]", which
returns zero as its own value when evaluated, and "[^0]", which returns
zero as the value of its home context when evaluated.  These are very
different things.  The general rule of thumb is that "returning" blocks
(those with "^" in them) should only be used when you know they will
cease to exist before or when their home context returns.  Their only
use within the Smalltalk-80 system as it is (aside from obvious uses as
arguments to "if", "while" and other vanilla control structures) is as
error-recovery parameters to other methods.  For example, it is common
to see uses like the following, where the method needs to find something
in the Dictionary "dict" and cannot continue if it isn't there:

	i <- dict at: key ifAbsent: [^$badError]

The "at:ifAbsent:" method sends "value" to its second argument if the
first argument is not a key in the receiver.

I hope this makes things clearer; let me know if it doesn't.

	Pavel Curtis
	ARPAnet and CSNet: Pavel.pa@PARC-MAXC
	UUCP: decvax!cornell!pavel
	BitNet: crnlcs%pavel

PS- The operational explanation above was verified on a Dorado running
ST80.  The philosophical one was generated by discussion with Dan
Ingalls of the Systems Concepts Lab (formerly the Software Concepts
Group, formerly the Learning Research Group, aka the Smalltalk group).