[Cuis-dev] Exception handler blocks with non-local returns

Andres Valloud ten at smallinteger.com
Sun Oct 13 19:51:18 PDT 2019


Hello, in regards to recent updates posted to the repository, it looks 
like we collectively forgot to do the cleanup needed for them to go in. 
In addition, it seems like we should have a bit more of a conversation 
before deciding whether the approach in that code is the way to go.

In the spirit of thinking things through, let's not engage in 
'solutioning', where the conversation is about deciding which 'solution' 
to pick.  Rather, let's define the problem first, then decide what a 
'solution' should look like, and finally do something about this.

So, for the sake of reference, I included my earlier observations about 
exception handlers with non-local returns further down.  If you read my 
previous note, feel free to skim that bit.  To summarize, however, the 
argument is mostly against using non-local return in exception handler 
blocks for the reasons explained.

Since then, a bit of investigation on what a cleanup would look like 
revealed that the use of non-local returns in exception handler blocks 
tends to be associated with code that is difficult to change.  This 
suggests there is at least some reason to believe using non-local 
returns in exception handler blocks is not preferable, generally 
speaking (chances are there will be some exceptional cases where it's 
worth the cost, I'm not convinced I've seen one of those yet).

Do we even agree this is a problem?

Andres.

================= arguments from previous email
The conversation that led to these changes went more or less like 
this... first, I noted that exception handlers that look like this:

	[:ex | ^5]

are asking for trouble, because:

a) effectively, nobody is looking at how the exception handling 
mechanism is implemented, so doing non-local return like that curtails 
the private implementation of exception handling,

b) that kind of behavior is what the messages #return, #resume, #pass, 
#outer, etc are there for,

c) when you need to debug why exceptions are behaving a certain way, the 
above exception handlers make debugging impossible because now there's 
no place to put a breakpoint (e.g. in #return, #resume, #pass, #outer, 
etc, which can be redefined for the purpose at hand in the single 
exception of interest), and finally,

d) it's possible to distort the exception handling mechanism behavior 
with non-local returns.


The vast majority of the time, I see exception handlers with non-local 
returns as very close to expressions such as these:

	#(1 2 3) asSortedCollection: [:x :y | ^x < y]

where the behavior you get is not what you expected, even though the 
code is doing exactly what was requested.  See attached for an actual 
example of what I mean.  To run the code, do these and see what happens.

	ExceptionExample new breakingStuff
	ExceptionExample new workingStuff

To me, generally speaking, these considerations push the scales against 
the apparent convenience of having exception handlers do a non-local 
return.  I do not want to have to debug that kind of convenience under 
time pressure.  I do not even want to have to change code that uses such 
convenience, because it's going to be more difficult than otherwise.
================= arguments from previous email

-------------- next part --------------
!classDefinition: #ExceptionExample category: #'ExceptionHandling-examples'!
Object subclass: #ExceptionExample
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'ExceptionHandling-examples'!

!ExceptionExample methodsFor: 'working stuff' stamp: 'sqr 10/13/2019 13:03:20'!
workingStuff

	| innerClosure |
	innerClosure := [:ex | ex return: #thwartedBadly].
	^self breakStuffWithInnerClosure: innerClosure! !


!ExceptionExample methodsFor: 'break stuff' stamp: 'sqr 10/13/2019 13:09:42'!
breakStuffWithInnerClosure: innerClosure

	| answer |
	answer := self protectedStuffToBreakWithInnerClosure: innerClosure.
	answer = #outer ifTrue: [^#workedWrong].
	answer = #thwartedBadly ifTrue: [^#thwartedProperly].
	^answer! !

!ExceptionExample methodsFor: 'break stuff' stamp: 'sqr 10/13/2019 13:06:03'!
breakingStuff

	| innerClosure |
	innerClosure := [:ex | ^#thwartedBadly].
	^self breakStuffWithInnerClosure: innerClosure! !

!ExceptionExample methodsFor: 'break stuff' stamp: 'sqr 10/13/2019 13:09:53'!
protectedStuffToBreakWithInnerClosure: innerClosure

	^[3 / 0.  #didNotFailAsItShould]
		on: ZeroDivide
		do: [:ex | innerClosure value: ex.  ex return: #outer]! !


More information about the Cuis-dev mailing list