Exceptions ========== An ``exception`` is an unexpected event that occurs during program execution (as opposed to problems detected during program compilation). One common type of exception is a violation of the contract of a function, such as attempting to divide a number by zero. Another example is an attempt to access an uninitialized slot, or certain cases of an attempt to violate the type constraint on a slot or variable (those that cannot be detected at compile time). Dylan detects all these exceptions itself. Sometimes, an application detects a violation of a contract that it defines. For example, in :ref:`slots-virtual-slots`, we defined methods that detected attempts to specify a longitude direction of anything other than east or west. (In :ref:`perform-enumerations` we changed the application such that this particular application-detected exception was transformed into one that is detected by Dylan.) When an unusual event occurs in an application, there are many options available for responding to that event. The application can try to handle the situation in its own particular way, or it can use the *exception protocol* defined by Dylan. In this chapter, we explore several approaches to providing an exception protocol between parts of an application. An informal exception protocol ------------------------------ Our goal is to modify the method that adds a ```` instance to a ```` instance. We redefine that method to detect overflow beyond the 24-hour period covered by a time of day, and to take special action in that case. In this section, we show a simple way to indicate and handle exceptions, without using the Dylan exception protocol. We then discuss the problems with this informal approach. In `A simple Dylan exception protocol`_, we achieve the same goal using Dylan conditions, and discuss the advantages of that approach. The ``+`` method using informal exceptions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First, we redefine the method for adding ```` and ```` (this method was last defined in :ref:`time-mod-time-implementation-file`). The method now returns an error string in the event that the computed sum is beyond the permitted 24-hour range: .. code-block:: dylan define method \+ (offset :: , time-of-day :: ) => (sum :: type-union(, )) let sum = make(, total-seconds: offset.total-seconds + time-of-day.total-seconds); if (sum >= $midnight & sum < $tomorrow) sum; else "time boundary violated"; end if; end method \+; We have altered the ``+`` method in two important ways. First, we have modified the original values declaration, ``(sum :: )``, to allow the return of either a ```` instance or a string describing a problem. Second, we have added code that checks the computed time of day, and returns an error string if the sum is out of bounds. To illustrate further how the informal exceptions work, we define a method that calls the ``+`` method defined in this section. We define a method, ``correct-arrival-time``, that adds predicted weather and traffic delays to an arrival time; and we define ``say-corrected-time``, which calls ``correct-arrival-time`` and displays the results: .. code-block:: dylan define method correct-arrival-time (arrival-time :: , weather-delay :: , traffic-delay :: ) => (sum :: type-union(, )) let sum1 = weather-delay + arrival-time; // Check whether the result of + was a string representing an error if (instance?(sum1, )) sum1; else // Otherwise, if there is no error, compute the second part of the sum traffic-delay + sum1; end if; end method correct-arrival-time; define constant $no-time = make(, total-seconds: 0); define method say-corrected-time (arrival-time :: , #key weather-delay :: = $no-time, traffic-delay :: = $no-time) => () let result = correct-arrival-time(arrival-time, weather-delay, traffic-delay); // Check whether the result of + was a string representing an error if (instance?(result, )) format-out("Error during time correction: %s", result); else // Otherwise, if there is no error, display the result say(result); end if; end method say-corrected-time; Problems with the informal exception protocol ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are several significant problems with the approach used in `The + method using informal exceptions`_: - As we saw in the ``correct-arrival-time`` method, most callers of the ``+`` function must check the type of the value returned. This type checking breaks up the normal flow of control, and gives as much weight to the unusual case (the exception) as it does to the usual case. If a caller fails to check the return value to see whether that value is a string, then a different error will occur later in the program (such as adding a string and time together), when it might be hard to trace back the problem to the original point of failure. Note that both direct callers of ``+`` (``correct-arrival-time``) and indirect callers of ``+`` (``say-corrected-time``) must understand and use this error protocol correctly. - For other methods that might return any object (including strings, for example), an additional return value would have to be used to indicate that an exception occurred. It would be easy to forget to check the extra return value and such failure could easily go undetected, causing unpredictable program behavior. If the method is being added to a generic function in another library, it might be impossible to add a second return value indicating failure, because the generic function might limit the number of return values. - A casual reader of the code could become easily confused about this ad hoc error protocol. Someone might inadvertently write code that did not obey this ad hoc protocol. Also, if all programmers use their own error protocols, it will be hard to remember which convention to obey at the call site; programmers will have to check the convention in the source code or programmer documentation. - In this example, the ability to restrict the return value to only ```` is lost. This loss might prevent compile-time error checking that could catch errors that would be difficult or inconvenient to catch at run time. It might also prevent the compiler from optimizing code that uses the results of this function, thus decreasing performance of the application. - We are limited in how we can respond to the error. The context in which the error was detected has been lost. There is no state we can examine to gather more details about the error, and to determine why the error occurred. We also cannot correct whatever caused the problem, then continue from the point where the error occurred. A simple Dylan exception protocol --------------------------------- In Sections `Signaling conditions`_ through `Continuation from errors`_, we show how to modify the three methods in `The + method using informal exceptions`_ to use the basic tools that Dylan provides for indicating and responding to exceptional situations. Signaling conditions ~~~~~~~~~~~~~~~~~~~~ Dylan provides a structured mechanism for indicating that an unusual event or exceptional situation has occurred during the execution of a program. Using this mechanism is called *signaling a condition*. A ``condition`` is an instance of the ```` class, which represents a problem or unusual situation encountered during program execution. To signal a condition, we need to take these steps: #. Define a condition class, which must be a subclass of ````. The condition class should have slots that are appropriate for the application. In this example, we define a condition class named ```` to be a direct subclass of ````. Note that ```` is a subclass of ````. We defined ```` to inherit from ````, because in case our application does not handle the exception, we want Dylan always to take some action, such as entering a debugger. If ```` inherited from ```` and the application failed to handle the exception, then the exception might simply be ignored. #. Modify the functions that might detect the exception. These functions must make an instance of the condition class, and must use an appropriate Dylan function to initiate the signaling process. In this example, we redefine the ``+`` method to signal the condition with the ``error`` function. In the following code, we define a condition named ```` to represent any kind of time error, and we define a condition named ```` to represent violations of time-of-day bounds. .. code-block:: dylan define abstract class () constant slot invalid-time ::