unwind-protect for newLISP

Started by rickyboy, April 13, 2007, 07:46:23 AM

Previous topic - Next topic

rickyboy

Hello everyone!



I took a shot at writing an unwind-protect macro for newLISP and I have hit the wall.  Here is the code (which doesn't work BTW):
(define-macro (unwind-protect EXPR POSTLUDE)
  (letex ($expr EXPR $postlude POSTLUDE)
    (let (result nil)
      (let (no-throw? (catch (let () $expr) 'result))
        $postlude
        (if no-throw? result (throw result))))))

I thought that I could use the return value from catch to sense if EXPR threw an exception, which I would then need to throw up the stack (after running POSTLUDE).  But it seems as if I can't control the exception  handling to this degree.



Am I missing something in my knowledge of newLISP exception handling or is an unwind-protect not possible in newLISP?  Curious.



BTW, I'd like to have an unwind-protect for various reasons; however a frequent use would be via a macro which handles file IO housekeeping, like CL's with-open-file, e.g.
(with-open-file (in "myfile" "read') bla bla bla ...)
where the file "myfile" gets automatically closed, even when a non-local exit is triggered in the blas.  (The symbol in would hold the file handle for  "myfile".)  And of course, my definition of with-open-file relies on unwind-protect.
(define-macro (with-open-file FLIST)
  (letex ($fhandle-id (FLIST 0)
          $path-file (FLIST 1)
          $access-mode (FLIST 2)
          $option (FLIST 3)
          $body (cons 'begin (args)))
    (let ($fhandle-id (open $path-file $access-mode $option))
      (unwind-protect
          $body
        (if (peek $fhandle-id) (close $fhandle-id))))))

Thanks for any help.  --Ricky
(λx. x x) (λx. x x)

rickyboy

#1
OK, I think I have the answer to this issue -- it's amazing what RTFM can do for you!  :-)



The good news is that the second form of catch will catch errors; however, there seems to be no way of detecting whether catch is catching a throw.  So I renamed no-throw? to no-error? and knocked out the re-throw.  So, now my macro looks like


(define-macro (unwind-protect EXPR POSTLUDE)
  "Try to act like CL's UNWIND-PROTECT as much as possible.  The
form of `catch' used here can catch both errors and exceptions;
the value of `no-error?' is not currently used (but may, in a
future version).  Too bad we cannot tell if `catch' is catching a
`throw' exception; then we could re-`throw' it after running
`$postlude'."
  (letex ($expr EXPR $postlude POSTLUDE)
    (let (result nil)
      (let (no-error? (catch $expr 'result))
        $postlude
        result))))


I used this lame test suite.


(define (test-error)
  "This test will cause an error by calling an undefined
function."
  (println "(setq filehandle (open file mode))")
  (unwind-protect
      (undefined-function 42)
    (println "(close filehandle)")))

(define (test-throw)
  "This test will throw an exception to the implicit `catch' in
`unwind-protect'."
  (println "(setq outfile (open file "write"))")
  (unwind-protect
      (begin (println "(write something outfile)")
    (throw 42)
    (println "Shouldn't see this message"))
    (println "(close outfile)")))


And this version of with-open-file seems to work after minimal testing (I am an awful tester -- too lazy :-).


(define-macro (with-open-file FLIST)
  (letex ($fhandle-id (FLIST 0)
          $open-call (cons 'open (1 FLIST))
          $body (cons 'begin (args)))
    (let ($fhandle-id $open-call)
      (unwind-protect
          $body
        (if (peek $fhandle-id) (close $fhandle-id))))))


Any comments?  Lutz (when you get back)?  Thanks!  --Rick
(λx. x x) (λx. x x)

Lutz

#2
Quote ...  however, there seems to be no way of detecting whether catch is catching a throw.


You can distinguish if a 'catch' returns because of an error or a 'throw' by its return value:


(catch (+ 1 x) 'result) => nil

result => "value expected in function + : x"

(catch (throw "foo") 'result) => true

result => "foo"


Only on error conditions will 'catch' return 'nil'. You could use 'throw-error' for generating user-defined errors.



To make the story complete see also the functions 'error-number' and 'error-text'. So when 'catch' returns 'nil' you could use 'error-number' and do 'case' on the result and then use 'throw-error' if you don't want to handle the error in the program. 'throw-error' does not need a 'catch' but can have one.


QuoteAny comments?


Emulating the behavior of other programming languages or LISPs in newLISP will most of the time lead to slower and harder to read programs. The same is true when people try to emulate the object oriented behavior of other languages in newLISP. And this is true for any programming language trying to emulate any other programming language.



You get better results when writing newLISP programs the newLISP way

Common LISP programs the Common LISP way, Scheme programs the Scheme way,

Perl programs the Perl,  way and so on ... ;-)



Lutz

rickyboy

#3
Lutz,



Thanks, as always, for your quick response.  Let me beg to differ on one small point, though, and please disabuse me of any wrong notions, but I still don't think it is possible for the program (not the programmer) to detect if catch was thrown to or not.  Neither the return value of catch nor the value in result indicate this.  For instance, both of these values are the same in the following, yet one catch is thrown to and the other is not.
(catch (throw 42) 'result) => true
result => 42

(catch 42 'result) => true
result => 42


Again, it's not possible for the program to detect this, even though I the programmer, in this instance, can plainly see that one throws yet the other does not.


Quote from: "Lutz"Emulating the behavior of other programming languages or LISPs in newLISP will most of the time lead to slower and harder to read programs ...


OK, you got me there.  :-)  You are right.  Another way of putting this is that I need to think harder to make my abstractions work the newLISP way (better for newLISP), rather than stealing the abstraction from another language, complete with its language-specific baggage.  And again in other words, I am being lazy.  :-)  I'll think about this some more.  Perhaps there is a better way to be gotten, or perhaps the particular abstraction is not *so* important.



Thanks again Lutz!  --Rick
(λx. x x) (λx. x x)

Lutz

#4
Quote... Let me beg to differ on one small point, ...


yes, you are right, I misunderstood, thinking you where referring to error conditions ;)



Lutz