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:
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'.