The condition system in Lisp is one of its best features. It accomplishes the same task as the exception handling mechanisms in Java, Python, and C++ but is more adaptable. In reality, its adaptability goes beyond error handling since conditions, which are more flexible than exceptions, can represent any event that occurs while a program is running and might be of interest to code at various levels on the call stack. The condition system is more flexible than exception systems because it divides the duties into three parts: signaling a condition, handling it, and restarting. Exception systems divide the duties into two parts: code that handles the issue and code that signals the condition..
A condition is an object whose class describes its general characteristics and whose instance data contains information on the specifics of the events that led to the condition being indicated. With the exception of the fact that the default superclass of classes defined with DEFINE-CONDITION is CONDITION rather than STANDARD-OBJECT, condition classes are defined using the DEFINE-CONDITION macro.
To define a condition:
Syntax:
(define-condition condition-name (error)
((text :initarg :text :reader text))
)
Catching any Condition (Handler-case):
A code used to handle the situation signaled there on is known as a condition handler. The erroring function is typically called from one of the higher level functions where it is typically defined. The signaling mechanism looks for a suitable handler when a condition is notified based on the condition’s class. Each handler has a type specifier that indicates the situations it may handle as well as a function that only accepts the condition as its only argument.
The signaling mechanism locates the most recent handler that is compatible with the condition type and calls its function whenever a condition is signaled. A condition handler is created by the macro Handler-case.
Syntax:
(handler-case (code that errors out)
(condition-type (the-condition)
;; <– optional argument
(code))
;;<– code of error clause
( another-condition (the-condition)
…))
The Handler-case returns its value if the code that encounters an error returns normally. A Handler-body case’s must consist of a single expression; PROGN can be used to aggregate many expressions into a single form. The code in the relevant error clause is executed, and its value is returned by the Handler-case, if the code that fails to execute signals a condition that is an instance of any of the condition-types listed in any error-clause.
Catching a Specific Condition:
By writing the condition-type, we may tell condition handlers what condition to handle. This process is comparable to a try/catch found in other programming languages, but we can accomplish more.
Example 1:
Lisp
;; LISP code for error handling
(handler - case ( / 3 0 )
(division - by - zero (c)
( format t "Caught division by zero: ~a~%" c)))
|
Output:
You will frequently encounter the following compiler warning if you keep the condition object as an argument but don’t access it in your handlers:
; caught STYLE-WARNING:
; The variable C is defined but never used.
Use a declare call as in the following to remove it:
(handler-case (/ 3 0)
(division-by-zero (c)
(declare (ignore c))
(format t “Caught division by zero~%”)))
;; we don’t print “c” here and don’t get the warning.
Handling conditions (Handler-bind):
Condition handlers can respond to a condition by activating the appropriate restart from the Restarting Phase, which is the code that really recovers your application from problems. The restart code is typically inserted into low-level or middle-level functions, while the condition handlers are positioned in the application’s higher levels. By using the Handler-bind macro, you can continue at lower level functions without having to unwind the function call stack and give a restart function. In other words, the lower level function will continue to have control over the process. The Handler-bind syntax is as follows:
(handler-bind ((a-condition #’function-to-handle-it)
;;<– binding
(another-one #’another-function))
(code that can…)
(…error out))
Each binding has a handler function with one argument and a condition type. The invoke-restart macro searches for and calls the most recent restart function that has been bound, passing the supplied name as an argument.
Example 2:
Lisp
;Lisp program to demonstrate defining
; a condition and then handling it using handler - bind
;If the divisor argument is zero,
; the program will produce an error situation.
;there are three anonymous functions(cases)
; offer three different strategies to overcome it.
; ( return 0 , recalculate using divisor 3
; and continue without returning)
(define - condition dividing - by - zero (error)
((message :initarg :message :reader message))
)
( defun div - zero - handle ()
(restart - case
( let ((result 0 ))
( setf result (div - func 24 0 ))
( format t "The value returned is: ~a~%" result)
)
(continue () nil )
)
)
( defun div - func (val1 val2)
(restart - case
( if ( / = val2 0 )
( / val1 val2)
(error 'dividing - by - zero :message "denominator is zero" )
)
(return - zero () 0 )
(return - val (x) x)
(recalculate - with (s) (div - func val1 s))
)
)
( defun high - level - code ()
(handler - bind
(
(dividing - by - zero
( format t "Error is: ~a~%" (message i))
(invoke - restart 'return - zero)
)
)
(div - zero - handle)
)
)
)
(handler - bind
(
(dividing - by - zero
( format t "Error is: ~a~%" (message i))
(invoke - restart 'return - val 0 )
)
)
)
(div - zero - handle)
)
(handler - bind
(
(dividing - by - zero
( format t "Error is: ~a~%" (message i))
(invoke - restart 'recalculate - with 3 )
)
)
)
(div - zero - handle)
)
(handler - bind
(
(dividing - by - zero
( format t "Error is: ~a~%" (message i))
(invoke - restart 'continue)
)
)
)
(div - zero - handle)
)
( format t "Finished executing all cases!" ))
|
Output:
Handler-case VS Handler-bind:
The try/catch forms used in other languages are comparable to handler-case. When we need complete control over what happens when a signal is raised, we should utilize a handler-bind. It restarts either interactively or programmatically and lets us use the debugger.
The fact that the handler function bound by Handler-bind will be invoked without unwinding the stack, keeping control in the call to parse-log-entry when this function is called, is a more significant distinction between Handler-bind and Handler-case. The most recent bound restart with the specified name will be found and invoked by the invoke-restart command. We can observe restarts (created by restart-case) wherever deep in the stack, including restarts established by other libraries whose functions this library called, if some library doesn’t catch all situations and allows some bubble out to us. And we can see the stack trace, which includes every frame that was called, as well as local variables and other things in some lisps. Everything is unwound once we forget about this after handling a case. The stack is not rewound by handler-bind.
Signaling (throwing) Conditions:
In addition to the “Condition System,” A number of functions are also available in common LISP that can be used to signal errors. However, how an error is handled once it has been reported depends on the implementation.
A message of error is specified by the user program. The functions analyze this message, and they might or might not show it to the user. Since the LISP system will handle them according to its preferred style, the error messages do not need to contain a newline character at either the beginning or end or to signal error. Instead, they should be created using the format function.
The following are a few often used routines for warnings, breaks, and both fatal and non-fatal errors:
error format-string &rest args
- A fatal error is indicated by it. Such errors cannot be recovered from, thus they never go back to the person who called them.
We can use error in two ways:
- (Error “some Text”): Indicates a Simple-Error condition
- (Error message: “We tried this and that, but it didn’t work.”)
cerror continue-format-string error-format-string &rest args
- It enters the debugger and raises an error. However, after fixing the issue, it enables program continuation from the debugger.
- cerror returns nil if the program is resumed after running into an error. The call to cerror is subsequently followed by the execution of the following code. This code ought to fix the issue, perhaps by asking the user for a new value if a variable was incorrect.
- The continue-format-string argument is provided as a control string to format along with the args to create a message string, similar to the way the error-format-string argument is.
warn format-string &rest args
- It typically does not enter the debugger but instead prints an error message.
- A warn implementation should take care of moving the error message to a new line before and after it, as well as maybe providing the name of the function that called warn.
break &optional format-string &rest args
- Without any chance of being intercepted by programmed error-handling tools, it outputs the message and enters the debugger right away.
- Break returns nil if continued. No parameters are required when using break, and a suitable default message will be delivered.
- It is assumed that using the break command to inject temporary debugging “breakpoints” into a program rather than to indicate problems will prevent any unexpected recovery actions from occurring. Break does not accept the additional format control string argument that cerror accepts, for this reason.
Example 3:
Lisp
; Lisp program to show signaling of
; condition using error function.
; Program calculates square root of a number
; and signals error if the number is negative.
( defun Square - root (i)
( cond ((or ( not (typep i 'integer)) (minusp i))
(error "~S is a negative number!" i))
((zerop i) 1 )
( t (sqrt i))
)
)
(write(Square - root 25 ))
(terpri)
(write(Square - root - 3 ))
|
Output:
Custom Error Messages:
So far, whenever an error is thrown or signaled, we saw a default text in the debugger which displayed the condition-type. This default error message in the debugger can be customized according to the wish of programmer with the help of :report function.
Default message in debugger:
Condition COMMON-LISP-USER::MY-DIVISION-BY-ZERO was signaled.
[Condition of type MY-DIVISION-BY-ZERO]
we can write the :report function in the condition declaration to specify the message that will be displayed in debugger when the condition is thrown.
Example:
(define-condition my-division-by-zero (error)
((dividend :initarg :dividend
:initform nil
:accessor dividend))
;; the :report is the message into the debugger:
(:report (lambda (condition stream)
(format stream “You were going to divide ~a by zero.~&” (dividend condition)))))
Message in debugger:
You were going to divide 3 by zero.
[Condition of type MY-DIVISION-BY-ZERO]
Conditions Hierarchy:
The hierarchy of different conditions is depicted below:
The following is the simple-error class precedence list:
- simple-error
- simple-condition
- error
- serious-condition
- condition
- t
The following is the simple-warning class precedence list:
- simple-warning
- simple-condition
- warning
- condition
- t
Similar Reads
Error Handling in Perl
Error Handling in Perl is the process of taking appropriate action against a program that causes difficulty in execution because of some error in the code or the compiler. Processes are prone to errors. For example, if opening a file that does not exist raises an error, or accessing a variable that
5 min read
File Handling in LISP
LISP is an acronym for list processing, it is one the oldest programming language currently being used in the field of artificial intelligence. It performs its computations on symbolic expressions which makes it too reliable for AI. File handling is a method by which we can access files and store da
4 min read
Rust - Error Handling
An error is basically an unexpected behavior or event that may lead a program to produce undesired output or terminate abruptly. Errors are things that no one wants in their program. We can try to find and analyze parts of the program that can cause errors. Once we found those parts then we can defi
5 min read
How to Handle list Error in R
R, a powerful and widely used programming language for statistical computing and data analysis, relies heavily on lists to store and manipulate data. However, working with lists in the R Programming Language may lead to errors if not handled properly. Table of Content Table of ContentsWhat is List ?
3 min read
Handling Errors in R Programming
Error Handling is a process in which we deal with unwanted or anomalous errors which may cause abnormal termination of the program during its execution. In R Programming, there are basically two ways in which we can implement an error handling mechanism. Either we can directly call the functions lik
3 min read
Exception handling in Julia
Any unexpected condition that occurs during the normal program execution is called an Exception. Exception Handling in Julia is the mechanism to overcome this situation by chalking out an alternative path to continue normal program execution. If exceptions are left unhandled, the program terminates
6 min read
How to Handle rep.int Error in R
To repeat items in a vector in R, one often uses the rep. int function. However, some factors might lead to problems while utilizing this function. Users can use rep. int to replicate items in vectors and debug problems including missing arguments, improper argument types, and mismatched vector leng
3 min read
How to Handle hist Error in R
Histograms are a fundamental tool in data analysis, providing a visual representation of the distribution of a dataset. However, when working with R Programming Language you may encounter errors while trying to create a histogram using the hist function. One common error is "x must be numeric." Here
4 min read
How to Handle setwd Error in R
In R Programming Language the setwd function is commonly used to specify the working directory. This is useful when working with files and directories in R, as it allows users to navigate to the desired location for reading or writing files. In this article, we'll explore what the setwd error is, wh
4 min read
Arrays in LISP
LISP, is a list processing, is a programming language widely used in working with data manipulation. LISP allows us to produce single or multiple-dimension arrays using the make-array function. An array can store any LISP object as its elements. The index number of the array starts from 0 to the n-t
2 min read