exception handling for Squeak available

Craig Latta Craig.Latta at NetJam.ORG
Tue Mar 2 02:41:40 UTC 1999


Hi--

	I've written an exception-handling framework for Squeak. 

	Release one gamma one is at http://www.netjam.org/self/projects/smalltalk/exceptionHandling.html. It's a complete implementation, including support for non-local returns (without requiring any change to the virtual machine). I tested it with Squeak 2.3. Please use it! :)

	It includes a hack to the parser and encoder, to make the compiler cope with temporary block variables. It doesn't actually produce properly-scoped closures (I'm working on that), it just creates temporary home method variables.

	The message interface is not the one used by ANSI Smalltalk; Richard Harmon is working on convenience methods for that (I hope this release makes that easier, Richard :).


	enjoy,

-C

***

	If you're new to exception handling, here's a brief tutorial... 

-- what are exceptions good for? --

	Exception handling is useful for dealing with asynchronous events, typically errors which may or may not occur in the course of evaluating an expression. For example, consider the following: 

	fraction := numerator / denominator

One doesn't know whether denominator will be zero. If it is, the division will be in error. How should we proceed? One approach is to check if denominator is zero before performing the division. In general, this adds a lot of unnecessary and obfuscatory code, and interrupts control flow. Another approach is to halt the division method when things go wrong. This is also undesirable, because it also interrupts control flow. 

	We can handle this situation without necessarily interrupting control flow, by using exception handling. The basic idea is to communicate information between contexts involved in an exceptional situation. Instead of halting the current process, the division method may request that a situation be handled. It makes this request to the current context: 

	thisContext handle: #divisionByZero

	This is where the magic begins. :) Contexts know how to search the context stack for a block closure to run for handling a situation. That block closure is called a handler. A handler is specified as a parameter in a special block-evaluation message, such as >>valueHandling:with:. For example: 

	fraction := (
		[numerator / denominator]
			valueHandling: #divisionByZero
			with: [:exception |
				Transcript show: 'division by zero
encountered, using nil instead.'
				nil])

	Now, when a division by zero occurs, fraction will be set to nil, and control proceeds uninterrupted. 

-- handler responses for manipulating process control flow --

	There are several control flow options available to a handler. A handler may reject, restart, proceed, or return. This is done by sending messages to the handler's exception parameter. An exception is a particular instance of a exceptional situation. 

	When a handler rejects an exception, the context in which handling was requested searches for a new handler further back on the context stack. This allows one to pass control to an outer handler. For example, you might want to handle a zero division situation in a particular method sometimes, and handle all numerical error situations in a sending method at other times. For example: 

	[
		[3 / 0]
			valueHandling: #divisionByZero
			with: [:exception |
				"Handle this as a more generic error."
				exception reject]
	]
		valueHandling: #numericError
		with: [:exception |
			"Control will proceed to this handler."
			Transcript show: 'division by zero attempted']

	When a handler restarts an exception, the searching context restarts the work block afresh. This is useful when a situation may be corrected by changing some state and starting over. For example: 

	[3 / denominator]
		valueHandling: #divisionByZero
		with: [:exception |
			denominator := 4.
			exception restart]

The receiver of >>valueHandling:with: above is known as the work block. There is a variant means of restarting which allows you to substitute a different work block for the original one: 

	[3 / denominator]
		valueHandling: #divisionByZero
		with: [:exception | exception restartDoing: [3 / 4]]

	When a handler proceeds an exception, control proceeds from the point after the evaluation of the work block: 

	[3 / denominator]
		valueHandling: #divisionByZero
		with: [:exception |
                        denominator := 4.
                        exception proceed].
	Transcript show: 'Control proceeds here.'

The same thing happens if a handler doesn't explicitly specify a control flow response to the exception. There are a couple of variants for proceeding: >>proceedAfterHandling: and >>proceedAnswering:. The first takes a situation name as a parameter, and causes control to proceed after handling another situation first. The second proceeds, answering its parameter for the value of the work block. Sending a normal >>proceed is equivalent to sending >>proceedAnswering with a parameter of nil. For example, fraction is assigned nil below: 

	fraction := (
		[3 / 0]
			valueHandling: #divisionByZero
			with: [:exception | exception proceed])

	Finally, when a handler returns an exception, the searching context returns control from the method in which the work block appears. For example: 

	[3 / 0]
		valueHandling: #divisionByZero
		with: [:exception | exception return].
	Transcript show: 'Control will not reach here.'

The value answered by the method is nil. A handler may use the variant >>returnAnswering:, which answers its parameter instead. Using >>return is equivalent to using >>returnAnswering: with a parameter of nil. 

	Multiple handlers may be installed in the same method for the same work block, for one or more situations: 

	[3 / denominator]
		valueHandling: #divisionByZero
		with: [:exception | exception returnAnswering: nil]
		and: #lowSpace
		with: [:exception | Transcript show: 'panic!']

	[3 / denominator]
		valueHandlingAnyOf: #(divisionByZero lowSpace)
		with: [:exception | exception returnAnswering: nil]

-- passing parameters to a handler --

	The context which requests handling may pass a parameter to handlers by using a variant of the request message: 

	thisContext handle: #divisionByZero with: 'some useful state'

	A handler may then access that parameter by sending parameter to the exception. A parameter passed from the point where a situation occurred is often very useful in resolving the situation. 

-- ensured behavior --

	One very important feature of this exception-handling framework is ensured behavior. This is a mechanism by which one may ensure the evalution of some expressions, despite interruption by the occurrence of a situation. For example, you might want to ensure that a socket is closed if an error interrupts the compilation of code streaming over it: 

	[^socketStream fileIn] valueEnsuring: [socketStream close]

Now, socketStream will be closed no matter what situations occur during the evaluation of the work block. Even the "non-local return" in the work block will not occlude the ensured block. The work block will also run even if the work block terminates the current process (because process termination is now done through exception handling). 


	Enjoy! 

	Craig Latta <Craig.Latta at NetJam.ORG> 
	1 March 1999



--
Craig Latta
composer and computer scientist
craig.latta at netjam.org
www.netjam.org
latta at interval.com
Smalltalkers do: [:it | All with: Class, (And love: it)]





More information about the Squeak-dev mailing list