parent previous next question (Smalltalk Textbook 25)

Signal & Exception

We cover 'Signal' (interrupt) and 'Exception' (exception handling) in this section. The pattern of using these is:

-----------------------------------------
aSignal
        handle: [:exception | "exception handling"]
        do: ["normal execution"]
-----------------------------------------

This program will handle an exception if a signal is raised during the execution of the do: block. Evaluate this program to clarify:


Program-25-1: (Object; errorSignal, handle:do:, exception)
----------------------------------------------------------
Object errorSignal
        handle: [:exception | Transcript cr; show: 'error',
                exception printString]
        do: [Object errorSignal raise]
-----------------------------------------------------------

The transcript should show 'error: anException'. To make an exception happen, you 'raise' a signal. Here is a more concrete example.


Program-25-2: (ArithmeticValue; divisionByZeroSignal, errorSignal, 
handle:do:, exception)
-----------------------------------------------------------------
ArithmeticValue divisionByZeroSignal
        handle: [:exception | Transcript cr; show: 'division by zero']
        do: [-2 to: 2
                        do:
                                [:n |
                                | v |
                                v := 123 / n.
                                Transcript cr; show: v printString]]
-----------------------------------------------------------------

The transcript should show 'division by zero' because the program tried to divide a value by zero. Smalltalk does not know how to do this and raises an exception. The program uses 'ArithmeticValue divisionByZeroSignal' instead of 'Object errorSignal'. However the program does not raise the signal intentionally as shown in the first example. The signal is raised by 'setNumerator:denominator' private message of 'Fraction' which detects that a value is supposed to be divided by zero.

When the signal is raised, Smalltalk searches for the context which sent 'handle:do:' to 'ArithmeticValue divisionByZeroSignal' most recently. Then it creates an exception to lazily evaluate a handler (block closure), the argument of 'handle:' Let's see what happens if you change 'ArithmeticValue divisionByZeroSignal' to 'Object errorSignal'.


Program-25-3: (Object; errorSignal, handle:do:, exception)
------------------------------------------------------------------
Object errorSignal
        handle: [:exception | Transcript cr; show: 'division by zero']
        do: [-2 to: 2
                        do:
                                [:n |
                                | v |
                                v := 123 / n.
                                Transcript cr; show: v printString]]
-------------------------------------------------------------------

You should see the same result as before. How about 'Object notFoundSignal'.


Program-25-4: (Object; notFoundSignal, handle:do:, exception)
---------------------------------------------------------------------
Object notFoundSignal
        handle: [:exception | Transcript cr; show: 'devision by zero']
        do: [-2 to: 2
                        do:
                                [:n |
                                | v |
                                v := 123 / n.
                                Transcript cr; show: v printString]]
---------------------------------------------------------------------

Now you should see an Exception window with a message like: 'Unhandled exception: Can't reate a Fraction with a zero denominator'. Close this notifier. I just wanted to show you that 'Object notFoundSignal' which is a receiver of 'handle:do:' does not do any exception handling if a division-by-zero signal is raised.

This happen because of the inheritance structure of instances of 'Signal'. Notice that the instance inheritance is different from the class inheritance. Program 25-5 prints the instance inheritance of 'Signal' on the transcript.


Program-25-5: (Dictionary, Signal; allInstances, instVarAt:, includesKey,
genericSignal)
-----------------------------------------------------------------
| signalDictionary signalCollection outputBlock |
signalDictionary := Dictionary new.
signalCollection := Signal allInstances
                                select: [:aSignal | aSignal hasName].
signalCollection
        do:
                [:aSignal |
                | parentSignal parentName signalName aCollection |
                parentSignal := aSignal instVarAt: 1.
                parentName := parentSignal printString.
                signalName := aSignal printString.
                (signalDictionary includesKey: parentName)
                        ifTrue:
                                [aCollection :=
                                        signalDictionary at: parentName.
                                aCollection add: signalName]
                        ifFalse:
                                [aCollection := SortedCollection new.
                                signalDictionary
                                        at: parentName
                                        put: aCollection.
                                aCollection add: signalName].
                (signalDictionary includesKey: signalName)
                        ifFalse:
                                [aCollection := SortedCollection new.
                                signalDictionary
                                        at: signalName
                                        put: aCollection]].
outputBlock :=
        [:signalName :nestLevel |
        | aCollection |
        Transcript cr.
        nestLevel timesRepeat: [Transcript show: '. . '].
        Transcript show: signalName.
        aCollection := signalDictionary at: signalName.
        aCollection
                do:
                        [:each |
                        outputBlock
                                value: each
                                value: nestLevel + 1]].
outputBlock
        value: Signal genericSignal printString
        value: 0
---------------------------------------------------------------------

The transcript should show:

----------------------------------------------------------
Signal genericSignal
. . Context emergencySignal
. . Object controlInterruptedSignal
. . . . Object haltSignal
. . . . Object notifySignal
. . . . Object userInterruptSignal
. . Object errorSignal
. . . . ArithmeticValue errorSignal
. . . . . . ArithmeticValue domainErrorSignal
. . . . . . . . ArithmeticValue coercionErrorSignal
. . . . . . . . ArithmeticValue divisionByZeroSignal
. . . . . . . . ArithmeticValue imaginaryResultSignal
. . . . . . ArithmeticValue rangeErrorSignal
. . . . . . . . ArithmeticValue overflowSignal
. . . . . . . . ArithmeticValue underflowSignal
. . . . . . ArithmeticValue unorderedSignal
. . . . BinaryObjectStorage errorSignal
. . . . . . BinaryObjectStorage formatErrorSignal
. . . . . . BinaryObjectStorage headerErrorSignal
. . . . ByteEncodedString unsupportedCharacterSignal
. . . . ClassBuilder errorSignal
. . . . . . ClassBuilder buildFailureSignal
. . . . . . ClassBuilder nilSuperclassSignal
. . . . ColorValue errorSignal
. . . . CompiledCode nPCMapErrorSignal
. . . . Context cannotResumeSignal
. . . . Context cannotReturnSignal
. . . . Context codeSimulationErrorSignal
. . . . Context primitiveFailedSignal
. . . . Controller badControllerSignal
. . . . ControlManager interruptLockedSignal
. . . . CType cantCoerceSignal
. . . . CType illegalAssignmentSignal
. . . . CType invalidArgumentSignal
. . . . ExternalStream cantReopenSignal
. . . . FontPolicy noMatchingFontSignal
. . . . GraphicsContext incompleteAreaCopySignal
. . . . KeyboardEvent nonCharacterSignal
. . . . Metaclass obsoleteSignal
. . . . Object messageNotUnderstoodSignal
. . . . Object notFoundSignal
. . . . . . Dictionary keyNotFoundSignal
. . . . . . . . Palette pixelNotFoundSignal
. . . . . . Dictionary valueNotFoundSignal
. . . . . . . . Palette paintNotFoundSignal
. . . . . . Object indexNotFoundSignal
. . . . . . . . Object nonIntegerIndexSignal
. . . . . . . . Object subscriptOutOfBoundsSignal
. . . . Object subclassResponsibilitySignal
. . . . ObjectMemory snapshotFailedSignal
. . . . OSErrorHolder errorSignal
. . . . ParagraphEditor compilationErrorSignal
. . . . Signal noHomeForReturnSignal
. . . . Signal proceedErrorSignal
. . . . Signal timeoutSignal
. . . . Signal wrongProceedabilitySignal
. . . . Stream errorSignal
. . . . . . Stream incompleteNextCountSignal
. . . . . . Stream positionOutOfBoundsSignal
. . . . UninterpretedBytes byteSwappingFailedSignal
. . . . WeakArray queueOverflowSignal
. . Object informationSignal
. . . . CodeStream restartSignal
. . . . Context baseContextSignal
. . . . ControlManager closedWindowSignal
. . . . Stream endOfStreamSignal
. . Process terminateSignal
. . Signal noHandlerSignal
----------------------------------------------------------

'Object errorSignal' ranks higher than 'ArithmeticValue divisionByZeroSignal', but 'Object notFoundSignal' is in another branch of the inheritance tree. So an exception handler is located using the instance inheritance of 'Signal'.

Let's examine 'Exception'. You get an 'Exception' as a block argument of a handler (block closure). You "do exception handling" by sending messages to the 'Exception'. Messages you can send to the 'Exception' are classify roughly into proceed, reject, restart, return.


Program-25-6: (Dictionary, Signal; allInstances, instVarAt:, includesKey,
genericSignal)
---------------------------------------------------------------
Object userInterruptSignal
        handle: [:exception | exception proceed]
        do: [1 to: 500 do:[:i | Transcript show: ' ', i printString]]
---------------------------------------------------------------

Program 25-6 prints numbers from 1 to 500 on the transcript. During printing, you can not interrupt by hitting control-C. Why? Because every time you do, this exception is handled by telling it to proceed. This is a typical example of 'proceed' to ignore signals.

Program 25-7 shows an example of 'reject'. It is convenient to delegate an exception process to a higher ranked handler. The transcript should show '1: error', '2: error', '3: error'.


Program-25-7: (ArithmeticValue, Signal; errorSignal, handle:do:, reject)
------------------------------------------------------------------
ArithmeticValue errorSignal
        handle:
                [:exception | Transcript cr; show: '3: error']
        do: [ArithmeticValue domainErrorSignal
                handle:
                        [:exception |
                        Transcript cr; show: '2: error'.
                        exception reject]
                do: [ArithmeticValue divisionByZeroSignal
                                handle:
                                        [:exception |
                                        Transcript cr; show: '1: error'.
                                        exception reject]
                                do: [123 / 0]]]
------------------------------------------------------------------

Program 25-8 uses 'restart' recover from an error by altering the data accessible viea the :exception argument and then 'try again' i.e. re-execute the block which raised the exception. You should see my last name on the transcript. Even though it was not in the dictionary at first, the exception handling mechanism placed it there and then reinvoked the at: message. The second time the at: message was sent, 'Atsushi' was in the dictionary.


Program-25-8: (Dictionary; keyNotFoundSignal, handle:do:, restart)
---------------------------------------------------------
| dictionary value |
dictionary := Dictionary new.
Dictionary keyNotFoundSignal
        handle: [:exception |
                dictionary at: exception parameter put: 'Aoki'.
                exception restart]
        do: [value := dictionary at: 'Atsushi'].
Transcript cr; show: value printString
---------------------------------------------------------

Program 25-9 uses 'return' to overide the block which caused the exception and return nil. You can return any object instead of nil by using "returnWith: '" For example, try "returnWith: 'Aoki'" in Program 25-9. This exception handling is good for default values.


Program-25-9: (Dictionary; keyNotFoundSignal, handle:do:, return)
----------------------------------------------------------------
| dictionary value |
dictionary := Dictionary new.
value := Dictionary keyNotFoundSignal
                handle: [:exception | exception return]
                do: [dictionary at: 'Atsushi'].
Transcript cr; show: value printString
----------------------------------------------------------------

Appropriate use of 'Signal' and 'Exception' will enhance the usability and robustness of your programs. However exception handling requires good analysis and design in which you consider all possible exceptions your program might encounter and carefully map these to the inheritance structure of 'Signal'.


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves