On #ifError: considered harmful was Re: Set>>add:

John Sarkela sarkela at cox.net
Wed Nov 13 00:56:28 UTC 2002


On Saturday, November 9, 2002, at 05:36 AM, Nevin Pratt wrote:

> The final draft of the ANSI standard (and presumably the actual 
> standard) specifies that if the argument to #add: for Set is nil, the 
> results are undefined.
>
> VisualWorks for this case just returns nil, and otherwise does nothing.
> Squeak throws an exception.
>
> GLORP appears to depend upon the VisualWorks behavior.  Since GLORP 
> has been ported to VA and Dolphin, I'm guessing that they also have 
> VW's behavior.  Thus, Squeak appears to be the odd-man out.
>
> I personally am not a fan of exceptions, and use them very sparingly, 
> if at all.  And in this case, I personally prefer VW's behavior.
>
> But I was curious what anybody else thought about this difference.

I've been meaning to write about appropriate use of exceptions in 
Squeak.

Design issues:

1. Need to conform to documented standards for cross dialect developers.
2. Need to conform to actual implementations because in practice,
theory and practice are entirely different things.
3. Need to conform to current platform behavior so as not to impact
existing platform specific code.

Proposed solution:
1. Create an abstract subclass of Error called CollectionError.
2. Create a concrete subclass of CollectionError called 
IllegalCollectionValueError.
3. Define IllegalCollectionValueError>>defaultAction as

defaultAction
	"Answer the undefined object since no object was added."

	^ nil

4. Redefine Set>>add:  as

add: newObject
	"Include newObject as one of the receiver's elements, but only if
	not already present. Answer newObject."

	| index |
	newObject ifNil:
		[IllegalCollectionValueError
			signal: 'Sets cannot meaningfully contain nil as an element'].
	index := self findElementOrNil: newObject.
	(array at: index) ifNil: [self atNewIndex: index put: newObject].
	^ newObject

Discussion:

At the core of this solution is the fact that in the Squeak exception 
mechanism design
there are *no* unhandled exceptions. If no one in the call chain has 
declared a handler
for a kind of exception, then the exception itself will execute its 
default action as the root
exception handler for the anomalous occurrence.

Thus, someone attempts to add nil to a Set. Set signals the 
IllegalCollectionValueError.
If someone has set an exception handler for a CollectionError their 
handler will intercept
and have the opportunity to respond to the exceptional occurrence. If 
no explicit
handler has been set, the default action will execute and a value of 
nil will be returned.
  quod erat demonstrandum

Issues:

There are issues in the base image that need rectification and are the 
motivation for the
subject line of this message. In the Squeak exception mechanism, it is 
never appropriate
to send the #ifError: message, nor is it appropriate to ever set a 
handler on Exception or
Error unless the handler passes the exception after intercept.

(Note: consider the design intention behind the #ifCurtailed: message. 
Almost
certainly this is what users of #ifError: truly intend. But I'll leave 
that discussion
for a more complete description of the exception mechanism that I have 
yet to write.)

A good concrete example of this occurred at Digitalk where this 
mechanism was designed
by the Portland team. When the 32 bit VM was ported to Win32s it was 
discovered that
when allocating say 10 file handles, the api would return success when 
in fact it had
allocated a number of handles strictly less than those requested, for 
example 7 were
actually allocated. When the 8th file handle needed to be used, a 
FileError would occur.
Interestingly enough, if one used the Win32s api once again one could 
allocate more file
handles, just never as many as requested at any one time. Thus, a known 
work around
for that error existed, ask Windows for more handles. We were able to 
define the
defaultAction for that class of FileError to recognize the condition, 
attempt to allocate
more handles and if successful, resume operation. If anyone in the call 
chain had said

["block with implicit file ops"]
	ifError: [self inform: 'oh my, things have gone horribly wrong!']

"OR"

["block with implicit file ops"]
	on: Error
	do: [:ex | self inform: 'oh my, things have gone horribly wrong!']

then the particular FileError in question would be caught, precluding 
the default handler
for that particular exception from ever having the opportunity to 
perform the known work
around to the particular problem.

A better expression would have been

["block with implicit file ops"]
	ifCurtailed:
		[self inform:
			'oh my, things have gone wrong, and no one could recover!']

Note that the argument to #ifCurtailed: is only evaluated when no one  
on the call chain, including
the default action, could handle or recover from the error. The point 
of #ifCurtailed: is to allow
the entire call chain plus default action to have a chance at recovery 
before unwinding
the call chain and if and only if the call chain is unwound, does the 
argument to #ifCurtailed:
actually get evaluated.

:-}> John Sarkela
"I know you've heard it before."  Dr. Winston O'Boogie




More information about the Squeak-dev mailing list