yet another *error* question

Mark Propst

Usenet Archive
Joined
May 2, 2000
Messages
492
Reaction score
0
Hi all you smart dudes and dudettes!

...example
command1 calls func1 which calls func2
func1 for example opens textfiles x y and z or sets variables 1 2 and 3 func2 opens text files a b and c or sets variables 4 5 and 6

....method1
func2 has a local *error* func that closes a b and c or resets 4 5 and 6 func1 has a local *error* func that closes x y and z or resets 1 2 and 3

....method2
command1 has a local *error* func that closes x y and z or resets 1 2 and 3 and also closes a b and c or resets 4 5 and 6

....questions
are methods 1 and 2 both valid?
if not, why is one or both not valid?
if so, is one preferred?
if not, which is correct? (if either)
or can only the top level function(command) have an error func?

tia
Mark
 
I guess the answer depends on how you want things to work. My (dubious) opinion is to employ one (1) *error* function in the "master" function, which to me is a
C: function, and should be written to handle what its sub-functions do, correctly or not.
 
John Uhden said:
I guess the answer depends on how you want things to work. My (dubious) opinion is to employ one (1) *error* function in the "master" function, which to me is a
C: function, and should be written to handle what its sub-functions do, correctly or not.

I'll bet on your *dubious* opinion any day!

I didn't know if you could have more than one 'layer' of error funcs. so each sub could take care of its own clean up and thus be more portable across routines, rather than the "master" function having to look 'inside' all the sub funcs to see what it has to include in it's 'master' error func. I'm thinking if an 'inner' function fails and has an error func that error func may run and be the last thing to happen, rather than passing 'upstairs' to the calling function's error handler, and so forth up the line to the 'master' error function.

I guess I'm kind of thinking 'out loud' cause I haven't tried experimenting yet.
gotta go find some time

maybe you could use one of those dynamic error handlers and each function could adderr it's own error code and delerr as it's last successful operation????

off to the lab....he he he the crazy scientist mumbles to himself.....
:-)

Thanks
Mark
 
I didn't know if you could have more than one 'layer' of error funcs.
so each sub could take care of its own clean up and thus be more portable across routines, rather than the "master" function having to look 'inside' all the sub funcs to see what it has to include in it's 'master' error func.

Very good. That's how its supposed to work. Portability or reusablity require that a procedure or function not know anything about the calling context, since it may be called from different places.

Unfortunately, Visual LISP has some very basic flaws in this area (exception handling), which make it next to impossible to have the same kind of localized, layered, non-exclusive exception handling, as most other languages permit.

The right way to solve the problem you cite is via exception handling and termination handling, but AutoDesk did a very lousy job at implementing the former in Visual LISP, and did not even consider the latter, at all.

To help understand why I've completely lost my patince with LISP, here's a basic example in Object Pascal/pseudo code.

To address the problem you cite in a language that has the required functionality (such as OP, C++ or any .NET language), I would use try/finally or try/except to deal with exceptions that require cleanup in multiple places or levels:

Procedure Proc1;
begin
Open(file1); // open a file
try
Proc2; // call another procedure
finally
Close(file1); // close the file
end;
end;

Procedure Proc2;
begin
Open(file2); // open another file
try
Proc3; // call another procedure
finally
Close(file2); // close the file
end;
end;

Procedure Proc3;
begin
Open(File3);
try
// <some process that may fail here>
finally
Close(File3);
end;
end;

Procedure Main;
begin
try
Proc1;
except
Print 'An error occured, all files closed.';
end;
end;

In the above, Proc1 calls Proc2, and Proc2 calls Proc3.

Each Procedure opens a file before it calls the next
Procecure, and closes the file after the called proc
returns. Regardless of whether an exception is raised
anywhere in that code, or not, all three files will be
closed.

That's because the code that appears between the
'finally' and 'end' statements is guaranteed to
execute, uncondtionally, even if an error occurs.

This is called "termination handling" (in contrast to
'exception handling', which is similar), a practice
that's commonly used in most professional development
languages that are in widespread use today (standard
equipment in Pascal, C++, and all .NET languages).

Unfortunately, Visual LISP's (vl-catch-xxxx) family
of error handling functions do not make this kind of
termination handling, or even structured exception
handling easy to emulate, and I will have no part of
trying to simulate what you see above, in VLISP.
 
thanks, that's what I figured and as my little test proved to me.

Tony Tanzillo said:
Very good. That's how its supposed to work. Portability or reusablity require that a procedure or function not know anything about the calling context, since it may be called from different places.

(defun test
(defun *error*(msg)
(print"error1")
(print msg)
)
(print "test1")
(test2)
)
(defun test2
(defun *error*(msg)
(print"error2")
(print msg)
)
(print "test2")
(test3)
)
(defun test3
(defun *error*(msg)
(print"error3")
(print msg)
)
(print "test3")
(setq bad (/ 6 0))
(print "you won't see this")
)

result:
"test1"
"test2"
"test3"

"error3"
"divide by zero"
_$
Thanks again
Mark
 
Mark,
I believe you understand Tony's point but the test functions you show don't prove much. You didn't localize any of the *error* functions, not that that would have changed the results in any way.

A partial workaround to AutoLISP weaknesses noted is possible but shouldn't be necessary and is hardly worth demonstrating since, except for interrupted file i/o, most error functions posted in this NG just restore system variables. With VLISP, it is entirely possible to avoid changing system variables. Therefore there is less need for the *error* functions than there was when command methods were the only choice.

If you want to implement a nested error structure, make the
*error* symbol local to the top level function. Then use a function to push sublevel error handlers on to an evaluation stack. Subfunction *error* statements should be pushed on to the stack in LIFO order. Though not nearly as neat and clean as the wonderful PASCAL language permits, it could provide much of its functionality.

Challenge: Code the following sub-functions: pusherror and poperror in the context below. Pusherror should build the
*error* function but not destroy the statements already there. Poperror should pop the local error handler off at the end of the subfunction. It should conditionally execute the popped error function. Remember that functions can be lists with the first argument being the argument list. You do not need defun to build functions.

Limitations: All nested user defined functions must use the pusherror / poperror structure or the whole house of cards fails.

(defun test( / *error* a )
(setq a 1)
;;at each level turn over the task of error/cleanup to pusherror/poperror (pusherror (list '(print"error1")'(print msg)(list 'print a))) (test2) ;;poperror only necessary if cleanup desired.
)

(defun test2( / a)
(setq a 2)
(pusherror (list '(print"error2")'(print msg) (list 'princ a))) (test3) (poperror nil) ;;nil means do not execute *error* as cleanup.
)

(defun test3( / a bad)
(setq a 3)
(pusherror (list '(print"error3")'(print msg)(list 'princ a))) (princ "test3") (setq bad (/ 6 0)) (print "you won't see this") (poperror nil)
)

Results should look like:
(test)
test3

"error3"
"divide by zero" 3
"error2"
"divide by zero" 2
"error1"
"divide by zero"
1

Of course, with more complex versions of pusherror and poperror, the error message could be printed only once. It should be possible to interpret from the above at least a method of closing files.

I look forward to seeing your solution.

Regards,
Doug
 
| Mark -
|
| Just my (perhaps feeble) opinion, but I think functions should always | clean up after themselves, so if something may be left untidy due to | abnormal termination, the function should have a _local_ error handler | to deal with that situation. | | (Where is Robert Bell?):)
|

Lurking... ;^)
 
This would be true for general use "toolbox" style functions for myself also.

Application specific subrs might use a localized error handler, but more likely use the application-level error handler.
 
Comments below the responses below (eek).

| Hi Herman,
| Good to hear from you. See responses below:
|
| | > Mark -
| >
| > Just my (perhaps feeble) opinion, but I think functions should always | > clean up after themselves, so if something may be left untidy due to | > abnormal termination, the function should have a _local_ error handler | > to deal with that situation. | | The valid point made about AutoLISP weakness was that the calling functions | don't really have a chance to clean up after themselves. It is not enough | to clean up at a particular level. The function at each level of the operating | stack should be given the opportunity to cleanup after itself. For AutoLISP, | without special programming, upper level functions are not given that chance.
|

A valid point, yes, and comment was even made earlier about (vl-catch-all-apply). As you obliquely mentioned, by using a properly constructed vl-catch-all-apply handler at the subr level, a return of nil could be used to permit the upper level functions to finish quietly.

Contrived sample:

(defun BadFile (/ FileH Success)
(setq FileH (open "C:\\Temp\\Test.txt" "w"))
(vl-Catch-All-Apply
(function
(lambda
(write-line (rtos "A") FileH)
(setq Success T))))
(close FileH)
Success)

(defun C:Test (/ *Error*)
(defun *Error* (msg)
(princ " <bonk!>")
(princ))
(princ "\nI'm happy...")
(if (BadFile) (princ "\nOk, you can go."))
(princ " I'm happy...")
(/ 1.0 0.0)
(princ))

| > if an error occurs in func, the local *error* will be called. Then, | > since func has ended (abnormally, in this case) all its local symbols, | > including local functions, go out of scope and are inaccessible. IOW, | > the local *error* function runs once, then dies. | | This is true, but without special programming, LISP does not integrate the | cleanup aspects of the local *error* of the calling function. Only the | lowest level gets cleaned up. | | <snip> | | > Based on what I just tried this evening (R2002) it appears to me that | > VLISP closes file handles if the symbol goes out of scope. | > | <snip> | | I think you will find that VLISP does not actually close the file, although, for | some reason, it allows further appends to occur. Try this experiment. While | Autocad is still running (but not program test2), try to delete the file created | with MS Explorer. Now close ACAD, reopen, rewrite the LISP to not error | out and close the file. Then try to delete the file with the Explorer. Note that, | unless the file is closed, another application may not use the file until ACAD | closes.

I'm glad you pointed that out. I'm surprised that Herman missed that part of the file open trouble. 8^)

| Tony raises a valid point of AutoLISP weakness. I only offered a partial | remedy. PASCAL, as demonstrated is certainly superior. | | The method of creating the error handlers via pusherror and | poperror must involve either defun-q or setq to construct the *error*'s. | So that each level can be popped. I would only add that consideration | should be made in looping situtations to avoid unneccessary activity in | creating and destroying error handlers. | | Regards, | Doug | |
 
NNOOOOOOO!!!!!!

It is sooo irritating not to be able to delete a file just because the "sloppiness" is possible.

8^)
 
Hi Robert,
Glad you joined in. Tony's point about the lack of layered error handling in VLISP rang true to me. Even though clever tricks and hacks can bring us closer to a good working system, there is still too much work to replicate what is built in in other languages. If a programmer is trying to maximize his programming profits, he would naturally select the tools that are the most powerful, reliable, and readable. Since I am not a full time programmer, I'm stuck with making what I have work as well as it can.

Your illustration of vl-catch... was relevant. It certainly has applications for making cleaner exits to busted code. Thanks for demonstrating that possibility. I wasn't suggesting vl-catch.... for the pusherror and poperror. Those two functions were intended just to rewrite and conditionally execute the
*error* function based on the current level. By building the *error* function as a list with the format

( (<argument list>) <statement group n> <statement group n-1>.... <statement group 1>) or ((msg)(progn ...)(progn....)(progn...))

using setq, it is possible to push each level's cleanup code on to the *error* stack and then pop it off at each function end. Poperror could conditionally execute only <statement group n> or just eliminate it from the list. At the top level, poperror would set *error* to nil.

I didn't post the actual functions because I thought others would have more fun experimenting with the idea themselves. The functions as I wrote them are simple and short.

The writers of AutoLISP could have helped by allowing the *error* handlers in each level to execute separately and in reverse sequential order. The stack is available to the LISP interpreter and these facilities could have been built in. Since we don't have access to that stack, we can only simulate it with a copy.

Regards,
Doug
 
Robert,
Unfortunately AutoCAD itself is guilty of this too.
Ever tried to debug a hatch pattern code and be forced
to shut down AutoCAD because it didn't close the pattern file after it reported an error? I have.

Herman,
Such problems are relatively unimportant for those of us that are writing and using the programs for ourselves but are very important to those who are selling their programs to others. For vendors, it is a matter of professional pride that their code not interfere and leave messes. I also imagine that such attention to those kinds of details is a coping mechanism to prevent constant service requests and criticisms.

Your other points are well taken about most toolbox functions not having or needing their own error handlers. I also agree with you about checking input such as making sure that denominators are not 0.

The nomex went over my head. ??? ;-)

Regards,
Doug
 
Mark,
You're certainly welcome. I admit that I didn't know how to interpret your "dynamic" comment. Have fun coming up with your own variety. Thanks for the use of your thread.

Regards,
Doug
 
Following up...

Maybe I'm just a simple person, but it just seems a whole lot simpler to have one *error* function, local to the top level call, that accounts for the possible errors in the sub-functions it employs, AND the sub-functions should be written in concert to minimize the error possibilities but to expose any symbols required for the singular *error* function to handle appropriately.

Thank you all for the intellectual offerings to this thread. It has made me more comfortable with the approach I've been using.
 
ok, here's one version...
I cheated cause I'm not using defun-q to change the error function definition...just a simple setq cons list and an extra function to evaluate it so again I'm probably disqualified from the contest but it does seem to print the end result you specified. I'll keep trying to get the defun-q think working when I get time

(defun test( / *error* a errlst)
(setq a 1)
(debugon)
(defun *error* (msg)
(if msg ;meets conditional test...ommited for simplicity (if errlst(evalerrlist errlst)) );i (princ) ) ;;at each level turn over the task of error/cleanup to pusherror/poperror (pusherror (list '(print"error1")'(print msg)(list 'princ a))) (test2) (princ) ;;poperror only necessary if cleanup desired.
)

(defun test2( / a)
(setq a 2)
(pusherror (list '(print"error2")'(print msg) (list 'princ a))) (test3) (poperror nil) ;;nil means do not execute *error* as cleanup.
)

(defun test3( / a bad)
(setq a 3)
(pusherror (list '(print"error3")'(print msg)(list 'princ a))) (princ "test3") (setq bad (/ 6 0)) (poperror nil)
)

(defun pusherror(arg)
(if arg
(if errlst
(setq errlst (cons arg errlst ))
(setq errlst (list arg))
)

)
);d

(defun poperror(arg)
(if (not arg)
(if errlst
(setq errlst (cdr errlst ))
)
(evalerrlist errlst)
)
)

(defun evalerrlist(errlst)
(if errlst
(mapcar
'(lambda (lst)
(mapcar 'eval lst)
)
errlst
)
)
(princ)
)
 
Hi Mark,
Good job. That seems to work fine. Since it looks like you're the only one interested, I post my versions of pusherror and poperror. I haven't researched past attempts like you have so I hope I don't step on anyone's toes by recreating prior posts. This post does not imply in any way that your version is any worse.

Don't think anything that works could be considered cheating. Perhaps you can post a link to the one's by Bobby and Vlad.

John and Robert had good points about application level error handlers. Most of the subroutines that I write that work as part of a specific application and subfunctions are merely written to make the top level code shorter and more readable. If subfunctions are involved in heavy looping activity, there would be a large performance penalty to constantly build and unbuild error handlers.

You ask good questions and its a pleasure to have interesting topics to discuss.

;;Pusherror-bare bones style. D. C. Broad, Jr. 8/22/03
;;Intended to build nested *error* functions. Assumes that any ;;prior *error* function was built with defun-q or setq. Will ;;choke if started with a prior SUBR style function bound to *error*. (defun pusherror (statements) (setq *error* (cond (*error* (cons '(msg) (cons (cons 'progn statements) (list (cons 'progn (cdr *error*)))))) (t (cons '(msg) statements)))))

;;Poperror-bare bones style. D. C. Broad, Jr. 8/22/03
(defun poperror (execute)
(if execute (eval (cons '(msg) (assoc 'progn *error*)))) (setq *error* (cons '(msg) (cdr (MEMBER (assoc 'progn *error*)*error*)))))

;;Enjoy.

Regards,
Doug

Mark Propst said:
ok, here's one version...
I cheated cause I'm not using defun-q to change the error function definition...just a simple setq cons list and an extra function to evaluate it so again I'm probably disqualified from the contest but it does seem to print the end result you specified. I'll keep trying to get the defun-q think working when I get time
<snip>
 

Latest threads

Back
Top