12.9: Handling Exceptions
- Page ID
- 45959
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)When an exception is signaled, the handler has several choices about how to handle it. In particular, it may:
- abandon the execution of the protected block by simply specifying an alternative result – it is part of the protocol but not used since it is similar to return;
- return an alternative result for the protected block by sending
return:
aValue
to the exception object; - retry the protected block, by sending
retry
, or try a different block by sendingretryUsing:
; - resume the protected block at the failure point by sending
resume
orresume:
; - pass the caught exception to the enclosing handler by sending
pass
; or - resignal a different exception by sending
resignalAs:
to the exception.
We will briefly look at the first three possibilities, and then take a closer look at the remaining ones.
Abandon the protected block
The first possibility is to abandon the execution of the protected block, as follows:
answer := [ |result| result := 6 * 7. Error signal. result "This part is never evaluated" ] on: Error do: [ :ex | 3 + 4 ]. answer → 7
The handler takes over from the point where the error is signaled, and any code following in the original block is not evaluated.
Return a value with return:
A block returns the value of the last statement in the block, regardless of whether the block is protected or not. However, there are some situations where the result needs to be returned by the handler block. The message return:
aValue
sent to an exception has the effect of returning aValue as the value of the protected block:
result := [Error signal] on: Error do: [ :ex | ex return: 3 + 4 ]. result → 7
The ANSI standard is not clear regarding the difference between using do: [:ex | 100 ]
and do: [:ex | ex return: 100]
to return a value. We suggest that you use return:
since it is more intention-revealing, even if these two expressions are equivalent in Pharo.
A variant of return:
is the message return
, which returns nil
.
Note that, in any case, control will not return to the protected block, but will be passed on up to the enclosing context.
6 * ([Errorsignal] on: Error do: [ :ex| ex return: 3 + 4]) → 42
Retry a computation with retry and retryUsing:
Sometimes we may want to change the circumstances that led to the exception and retry the protected block. This is done by sending retry
or retryUsing:
to the exception object. It is important to be sure that the conditions that caused the exception have been changed before retrying the protected block, or else an infinite loop will result:
[Error signal] on: Error do: [:ex | ex retry] "will loop endlessly"
Here is another example. The protected block is re-evaluated within a modified environment where theMeaningOfLife
is properly initialized:
result := [ theMeaningOfLife * 7 ] "error -- theMeaningOfLife is nil" on: Error do: [:ex | theMeaningOfLife := 6. ex retry ]. result → 42
The message retryUsing: aNewBlock
enables the protected block to be replaced by aNewBlock
. This new block is executed and is protected with the same handler as the original block.
x := 0. result := [ x/x ] "fails for x=0" on: Error do: [:ex | x := x + 1. ex retryUsing: [1/((x-1)*(x-2))] "fails for x=1 and x=2" ]. result → (1/2) "succeeds when x=3"
The following code loops endlessly:
[1 / 0] on: ArithmeticError do: [:ex | ex retryUsing: [ 1 / 0 ]]
whereas this will signal an Error
:
[1 / 0] on: ArithmeticError do: [:ex | ex retryUsing: [ Error signal ]]
As another example, keep in mind the file handling code we saw earlier in which we printed a message to the Transcript when a file is not found. Instead, we could prompt for the file as follows:
[(StandardFileStream oldFileNamed: 'error42.log') contentsOfEntireFile] on: FileDoesNotExistException do: [:ex | ex retryUsing: [FileList modalFileSelector contentsOfEntireFile] ]
Resuming execution
A method that signals an exception that isResumable
can be resumed at the place immediately following the signal. An exception handler may therefore perform some action, and then resume the execution flow. This behavior is achieved by sending resume:
to the exception in the handler. The argument is the value to be used in place of the expression that signaled the exception. In the following example we signal and catch MyResumableTestError
, which is defined in the Tests-Exceptions category:
result := [ | log | log := OrderedCollection new. log addLast: 1. log addLast: MyResumableTestError signal. log addLast: 2. log addLast: MyResumableTestError signal. log addLast: 3. log ] on: MyResumableTestError do: [ :ex | ex resume: 0 ]. result → an OrderedCollection(1 0 2 0 3)
Here we can clearly see that the value of MyResumableTestError
signal is the value of the argument to the resume:
message.
The message resume is equivalent to resume: nil
.
The usefulness of resuming an exception is illustrated by the following functionality which loads a package. When installing packages, warnings may be signaled and should not be considered fatal errors, so we should simply ignore the warning and continue installing.
The class PackageInstaller
does not exist, though here is a sketch of a possible implementation.
PackageInstaller»installQuietly: packageNameCollection .... [ self install ] on: Warning do: [ :ex | ex resume ].
Another situation where resumption is useful is when you want to ask the user what to do. For example, suppose that we were to define a class ResumableLoader
with the following method:
ResumableLoader»readOptionsFrom: aStream | option | [aStream atEnd] whileFalse: [option := self parseOption: aStream. "nil if invalid" option isNil ifTrue: [InvalidOption signal] ifFalse: [self addOption: option]].
If an invalid option is encountered, we signal an InvalidOption
exception. The context that sends readOptionsFrom:
can set up a suitable handler:
ResumableLoader»readConfiguration | stream | stream := self optionStream. [self readOptionsFrom: stream] on: InvalidOption do: [:ex | (UIManager default confirm: 'Invalid option line. Continue loading?') ifTrue: [ex resume] ifFalse: [ex return]]. stream close
Note that to be sure to close the stream, the stream close
should guarded by an ensure:
invocation.
Depending on user input, the handler in readConfiguration
might return nil
, or it might resume
the exception, causing the signal message send in readOptionsFrom:
to return and the parsing of the options stream to continue.
Note that InvalidOption
must be resumable; it suffices to define it as a subclass of Exception
.
You can have a look at the senders of resume:
to see how it can be used.
Passing exceptions on
To illustrate the remaining possibilities for handling exceptions such as passing an exception, we will look at how to implement a generalization of the perform:
method. If we send perform:
aSymbol
to an object, this will cause the message named aSymbol to be sent to that object:
5 perform: #factorial → 120 "same as: 5 factorial"
Several variants of this method exist. For example:
1 perform: #+ withArguments: #(2) → 3 "same as: 1 + 2"
These perform:
-like methods are very useful for accessing an interface dynamically, since the messages to be sent can be determined at run-time. One message that is missing is one that sends a cascade of unary messages to a given receiver. A simple and naive implementation is:
Object»performAll: selectorCollection selectorCollection do: [:each | self perform: each] "aborts on first error"
This method could be used as follows:
Morph new performAll: #( #activate #beTransparent #beUnsticky)
However, there is a complication. There might be a selector in the collection that the object does not understand (such as #activate
). We would like to ignore such selectors and continue sending the remaining messages. The following implementation seems to be reasonable:
Object»performAll: selectorCollection selectorCollection do: [:each | [self perform: each] on: MessageNotUnderstood do: [:ex | ex return]] "also ignores internal errors"
On closer examination we notice another problem. This handler will not only catch and ignore messages not understood by the original receiver, but also any messages sent and not understood in methods for messages that are understood! This will hide programming errors in those methods, which is not our intent. To address this, we need our handler to analyze the exception to see if it was indeed caused by the attempt to perform the current selector. Here is the correct implementation.
Code \(\PageIndex{1}\) (Pharo):
Object»performAll: selectorCollection selectorCollection do: [:each | [self perform: each] on: MessageNotUnderstood do: [:ex | (ex receiver == self and: [ex message selector == each]) ifTrue: [ex return] ifFalse: [ex pass]]] "pass internal errors on"
This has the effect of passing on MessageNotUnderstood
errors to the surrounding context when they are not part of the list of messages we are performing. The pass message will pass the exception to the next applicable handler in the execution stack.
If there is no next handler on the stack, the defaultAction
message is sent to the exception instance. The pass
action does not modify the sender chain in any way — but the handler that controls it to may do so. Like the other messages discussed in this section, pass
is special — it never returns to the sender.
The goal of this section has been to demonstrate the power of exceptions. It should be clear that while you can do almost anything with exceptions, the code that results is not always easy to understand. There is often a simpler way to get the same effect without exceptions; see Code 13.15.1 on page 289 for a better way to implement performAll:
.
Resending exceptions
Suppose that in our performAll:
example we no longer want to ignore selectors not understood by the receiver, but instead we want to consider an occurrence of such a selector as an error. However, we want it to be signaled as an application-specific exception, let’s say InvalidAction
, rather than the generic MessageNotUnderstood
. In other words, we want the ability to “resignal” a signaled exception as a different one.
It might seem that the solution would simply be to signal the new exception in the handler block. The handler block in our implementation of performAll:
would be:
[:ex | (ex receiver == self and: [ex message selector == each]) ifTrue: [InvalidAction signal] "signals from the wrong context" ifFalse: [ex pass]]
A closer look reveals a subtle problem with this solution, however. Our original intent was to replace the occurrence of MessageNotUnderstood
with InvalidAction
. This replacement should have the same effect as if InvalidAction
were signaled at the same place in the program as the original MessageNotUnderstood
exception. Our solution signals InvalidAction
in a different location. The difference in locations may well lead to a difference in the applicable handlers.
To solve this problem, resignaling an exception is a special action handled by the system. For this purpose, the system provides the message resignalAs:
. The correct implementation of a handler block in our performAll:
example would be:
[:ex | (ex receiver == self and: [ex message selector == each]) ifTrue: [ex resignalAs: InvalidAction] "resignals from original context" ifFalse: [ex pass]]