Arc Forumnew | comments | leaders | submitlogin
Catching and inspecting exceptions
1 point by Pauan 4997 days ago | 18 comments
Okay, this is really annoying. First, what I'm trying to do: write a function that calls code that could potentially raise an exception. Okay, no problem, just use errsafe... but there's a catch. I also want to access whatever it was that was raised. So, if the function calls (err "foo") I want to be able to access the "foo" part[1].

This is very very very easy to do in Python (and JavaScript):

  try:
      raise Exception("foo")
  except Exception as e:
      print str(e) # prints the string "foo"
After some poking and prodding, I made a tiny bit of progress. First, the on-err function lets you get access to the exception, so I tried that:

  (on-err (fn (x) (prn x))
          (fn () (err "foo")))

  ; prints #(struct:exn:fail foo #<continuation-mark-set>)
Okay, so exceptions are structs... I then tried looking up the Racket documentation for how to access the struct's attributes, but no dice. I tried this:

  ((struct->vector c) 1) ; c is the exception
But I get the following error:

  procedure application: expected procedure, given: #(struct:exn:fail:contract "procedure application: expected procedure, given: #(struct:exn:fail \"foo\" #<continuation-mark-set>); arguments were: 1" #<continuation-mark-set>); arguments were: 1
Which is about as helpful as ... well, it isn't helpful. Okay, so, did some more reading... learned about vector-ref (gosh Racket is so much clunkier than Arc is...):

  (vector-ref (struct->vector c) 1) ; another error, yay
Okay, I just found exn-message... this really is a lot clunkier than it could be. sigh I feel kinda silly now, but I'm gonna leave this post here, in case somebody else has the same problem. I had already looked at the Exceptions documentation, but somehow missed that function. To my credit, the documentation for exn-message is pretty much... nonexistent. I accidentally learned about it while reading ac.scm.

I think we should figure out a better way of dealing with exceptions... I'm definitely going to make exceptions a lot easier to deal with in Arubic, which should be a piece of cake (hopefully) since Python already has such nice support for them.

An idea: uncaught exceptions are simply output to stderr, so you can use w/stderr or similar to catch them. Also, if you want to send special information in the exception, you should be using a complex data type anyways:

  (err (obj ...))
Then we can get rid of the `details` function. Yay less primitives! And let's change errsafe so it accepts a second parameter: a function that is called if the code raises an exception:

  (errsafe ... (fn (x) x))
The default would be to return the exception, since I think that's what you want, most of the time:

  (errsafe (err "foo"))    -> "foo"
  (errsafe (err '(1 2 3))) -> (1 2 3)
  (errsafe (+ 3 4))        -> 7
---

* [1]: In case you're wondering why I want to do this, here's the more detailed explanation. I'm writing a function that filters a list (the input) according to another list (the patterns). The default behavior is to throw an error when a pattern doesn't match anything in the input, or when a pattern matches twice, etc.

You can override this behavior by passing in a function... your function will be called once for every missing pattern. This all works fine, no problem, but what I want it to do is, rather than display one error and then fail... I want it to display all the errors.

I figured I could use something like on-err to collect the errors into a list, and then display them all at once. But if I can't access what was thrown, I obviously can't do that. Basically, I want this to work:

  (= errors nil)

  (each ...
    (push (errsafe (foo)) errors))
            
  errors -> ("foo" "bar" "qux")
Assume that the function foo throws the errors "foo", then "bar", then "qux". To get this working, I only had to change one line in arc.arc:

  (mac errsafe (expr)
    `(on-err (fn (c) (details c))
             (fn () ,expr)))
Note the call to `details`, rather than returning nil. Hurray for taking over an hour just to figure out the single line I needed to change... this really isn't helping my gut feeling that Scheme is too bloated and hard to work with. :P


1 point by rocketnia 4997 days ago | link

"Okay, I just found exn-message... this really is a lot clunkier than it could be. sigh I feel kinda silly now, but I'm gonna leave this post here, in case somebody else has the same problem. I had already looked at the Exceptions documentation, but somehow missed that function. To my credit, the documentation for exn-message is pretty much... nonexistent. I accidentally learned about it while reading ac.scm."

In the documentation for the 'exn struct (http://docs.racket-lang.org/reference/exns.html?q=exn#(def._...), they show that the fields are "message" and "continuation-marks". They don't document 'exn-message and 'exn-continuation-marks because those are just generic struct getters. It's as though 'struct is a macro and 'exn-message and 'exn-continuation-marks are generated automatically, but I think it's a bit more integrated into the module system than that.

There are several other variables a (struct ...) form automatically generates, and they're described here: http://docs.racket-lang.org/reference/define-struct.html

I'm not trying to claim the Racket documentation is easy to read or anything, but I hope this helps you out in the future. ^_^

---

"this really isn't helping my gut feeling that Scheme is too bloated and hard to work with. :P"

That may be true, but Racket is much more complicated than Scheme has to be, and that's not a mistake.

http://racket-lang.org/new-name.html

Sure, it has parentheses, uses the keyword lambda, provides lexical scope, and emphasizes macros — but don't be fooled. PLT Scheme is no minimalist embodiment of 1930s math or 1970s technology. PLT Scheme is a cover for a gang of academic hackers who want to fuse cutting-edge programming-language research with everyday programming. They draw you in with the promise of a simple and polite little Scheme, but soon you'll find yourself using modules, contracts, keyword arguments, classes, static types, and even curly braces.

Incidentally, the very same concern for comprehensiveness, practicality, and compatibility that (I believe) has damaged the purity of Racket's design makes it a nice platform for implementing Arc. That's no mistake either; Racket is designed to support lots of sublanguages besides Scheme, like Typed Racket, the stateless servlet language, etc.

-----

1 point by Pauan 4997 days ago | link

"In the documentation for the 'exn struct [...]"

Yes... I picked up on that... after stumbling upon exn-message in ac.scm. I did try skimming the struct documentation as well, but it doesn't seem to tell you how to access the fields.

From what I can tell with exn-message, if you create a struct "foo", with a field "bar", it will automatically create a function "foo-bar" to access it?

That's really unintuitive/clunky in my opinion. Especially compared to say... Arc, where it would just be (foo 'bar).

---

"That may be true, but Racket is much more complicated than Scheme has to be, and that's not a mistake."

Yes, but Racket feels like it's more complicated than it needs to be. Then again, perhaps there's somebody out there that needs this fancy-pants exception stuff. :P

And even though Scheme may be more minimal, it still has a few parts that seem bloated, like (let ((a 1) (b 2))) which is pretty unfortunate since I would expect `let` blocks to be pretty common! Thus, I see Arc as being more minimal than Scheme, in some regards.

-----

1 point by rocketnia 4997 days ago | link

"From what I can see with exn-message, if you create a struct "foo", with a field "bar", it will automatically create a function "foo-bar" to access it?"

I think that's what it says, though it isn't especially clear about it.

I linked you to the reference, which is targeted at people who already generally know what's going on but need specific information. Here's a guide entry: http://docs.racket-lang.org/guide/define-struct.html

---

"Especially compared to say... Arc, where it would just be (foo 'bar)."

Racket structs are not the same as hash tables. For instance, they can have inheritance, and they can override 'equal? using 'prop:equal+hash (http://docs.racket-lang.org/reference/structures.html). They're not especially orthogonal features, but structs are specifically for custom data types, while hash tables are for associations.

For extra fun, Racket also has classes (http://docs.racket-lang.org/guide/classes.html). :-p

---

"And even though Scheme may have been designed for minimalism, it still has a few parts that seem bloated, like (let ((a 1) (b 2))) which is pretty unfortunate since I would expect `let` blocks to be pretty common!"

Actually, I think that kind of let block is more consistent with the goals of Racket. It makes it easier for Racket to give detailed error messages when part of the let is missing.

Of course, the Arc approach is to make the syntax get out of the way so we can identify the error before it gets to the compiler. :-p

-----

1 point by Pauan 4997 days ago | link

"I linked you to the reference, which is targeted at people who already generally know what's going on but need specific information. Here's a guide entry: http://docs.racket-lang.org/guide/define-struct.html "

Oh neat. I guess I was expecting documentation somewhat more like Python, where they're more likely to tell you that kinda stuff in the docs, rather than requiring you to go read the tutorial. :P That could come in handy, thanks.

---

"Racket structs are not the same as hash tables. For instance, they can have inheritance [...]"

Cough. Prototypes. Cough.

---

"[...] They're not especially orthogonal features, but structs are specifically for custom data types, while hash tables are for associations."

Except in Arubic you do use tables to make custom data types. :P Besides, that's not really the point. I'm not saying structs need to be implemented as hash tables, just that you could call them like as if they were hash tables. So rather than using (exn-message foo) you'd use (foo 'message).

Arc overloads function calling on tables, lists, strings, etc. So why couldn't they have done that with Racket? I guess one reason would be for type checking... Racket seems to be the kinda language where they prefer strong types. In which case, okay, I can't really argue against that, if that's Racket's goal. Still feels clunky to me.

Interestingly enough... the way things are going with Arubic, it looks like I'm essentially copying Lua. Lua puts a strong emphasis on tables, similar to how Lisps put a strong emphasis on lists. It also has prototypical inheritance. It also uses foo.x as syntax sugar for foo["x"], which is similar to Arc:

  (foo 'x)
  foo.'x
JavaScript also shares those properties with Lua, so I guess you could also say I'm copying JavaScript... but only the parts I like, which are primarily objects and prototypical inheritance (Arc already has lambdas and closures).

I've also considered combining tables and lists together, somehow... something like this: http://arclanguage.org/item?id=4237

---

"Actually, I think that kind of let block is more consistent with the goals of Racket. It makes it easier for Racket to give detailed error messages when part of the let is missing."

How so? How does (let ((a 1) (b 2))) give more information than (let (a 1 b 2))?

Besides, I'm talking about the `let` in Scheme, so the whole "it makes sense in Racket!" thing doesn't really apply. :P How does it make sense in Scheme?

-----

1 point by rocketnia 4996 days ago | link

"I guess I was expecting documentation somewhat more like Python, where they're more likely to tell you that kinda stuff in the docs, rather than requiring you to go read the tutorial. :P"

It's not an organizational thing. Here's where they explain it the reference:

  A struct form with n fields defines up to 4+2n names:
  
  - [...]
  
  - id-field-id, for each field; an accessor procedure that takes an instance of
    the structure type and extracts the value for the corresponding field.
The reference even links to the guide, for what it's worth.

But yeah, the reference is very succinct, and the docs are altogether too scattered. Even regular Racket programmers feel that way: http://www.mail-archive.com/dev@racket-lang.org/msg00957.htm...

---

"Cough. Prototypes. Cough."

Depending on the context, the idea of a hash table with a prototype may be a contradiction in terms. For instance, if by "hash table" we refer to exactly the hash table implementation Racket has today, then it simply has no prototypes. :-p

There are contexts where it does make sense, particularly when talking about hypothetical languages, but if a single language tried to make sense of every idea people had for it, it'd be pretty darn bloated. ^_^

---

"I'm not saying structs need to be implemented as hash tables, just that you could call them like as if they were hash tables. So rather than using (exn-message foo) you'd use (foo 'message).

"Arc overloads function calling on tables, lists, strings, etc. So why couldn't they have done that with Racket?"

Actually, they do. A Racket struct can implement the 'prop:procedure structure type property to specify how it should behave when called like a procedure (http://docs.racket-lang.org/reference/procedures.html?q=prop...).

In fact, it can also implement 'prop:dict to behave like a dictionary (http://docs.racket-lang.org/reference/dicts.html?q=prop%3A#(...). Hash tables and association lists are other things that can be used as dictionaries.

---

"How does (let ((a 1) (b 2))) give more information than (let (a 1 b 2))?"

I'm not altogether sure myself, but here's what the docs have to say: http://docs.racket-lang.org/syntax/stxparse-intro.html

That page tries to demonstrate how Racket's 'syntax-parse and syntax classes make it possible to write macros with readable implementations and good, consistent error messages. The example it uses is a reimplementation of 'let.

In my own opinion, what that page actually demonstrates is how a misplaced emphasis on readable implementations (code-as-the-spec) can lead to more complexity and less readable code overall. :-p

But to be fair, for lots of people (especially people who are learning programming, a big target audience of Racket), the better error messages improve the maintenance experience much more than verbosity detracts from it. I occasionally ask random not-necessarily-programmer people what they want in a programming language, just to get some ideas, and the second most common response is "Better error messages." (The most common response is "How should I know?!" :-p )

---

"Besides, I'm talking about the `let` in Scheme, so the whole "it makes sense in Racket!" thing doesn't really apply. :P How does it make sense in Scheme?"

Beats me. :) IMO, Arc simply makes more sense than Scheme, and that's fine since Scheme came first.

-----

1 point by Pauan 4996 days ago | link

"Depending on the context, the idea of a hash table with a prototype may be a contradiction in terms. For instance, if by "hash table" we refer to exactly the hash table implementation Racket has today, then it simply has no prototypes. :-p

There are contexts where it does make sense, particularly when talking about hypothetical languages, but if a single language tried to make sense of every idea people had for it, it'd be pretty darn bloated. ^_^"

How so? JavaScript and Lua both add prototypes to tables/objects. It's possible to implement it in Arc right now, it's just not possible to make them blend seamlessly into the language, which is one thing I plan to fix with Arubic.

A prototype isn't defined by it's implementation, it's defined by it's idea. You could define an alist or plist to be a prototype, or an AVL tree, or who knows what else. Heck, I was using functions to represent prototypes earlier. :P There are only two requirements for prototypes in my mind: message passing, and, when data is not found in the data structure, it will check it's parent rather than failing[1].

To put it another way, you can use delegation and message passing to implement the concept of prototypes. And since we're talking about Arubic here, I can implement prototypes with just about any compound data type. :P

So when I mentioned prototypes, I was referring to the fact that it is indeed possible to give inheritance to hash tables, and so although you're right that structs are not the same as hash tables (at least in Racket), I'm pointing out that they could be the same, which is the direction I'm going in Arubic.

Actually, you could probably treat hash tables as prototypes in Racket too, but you would need to use special functions to access them:

  (proto-get foo 'bar)
  (proto-set foo 'bar 'qux)
---

"Actually, they do. A Racket struct can implement the 'prop:procedure structure type property to specify how it should behave when called like a procedure.

In fact, it can also implement 'prop:dict to behave like a dictionary. Hash tables and association lists are other things that can be used as dictionaries."

Ah, I see, so it's basically a bigger and more complicated version of the message passing in Arubic. I still prefer Arubic. :P

---

* [1]: Technically speaking, you could implement prototypes by copying the data, rather than using delegation... but when I refer to prototypes, it is referring to the delegation-based model, as that's what JavaScript and Lua use.

Also, I do draw a distinction between classes and prototypes, even though both use message passing and inheritance. The distinction isn't a very huge one, though. Instances inherit from a class, and a class inherits from other classes. Interestingly enough, it's possible to emulate classes using prototypes, but not possible to emulate prototypes using (static) classes... thus you could say that prototypes are a superset of classes.

It is possible to emulate prototypes in a language with dynamic classes though, like Python. It just requires you to create a new class at runtime, then create a new instance. So every class would have precisely one instance. This is still kinda kludgy, though... for instance, Python requires some special properties to be defined on classes... you can't add them to instances.

-----

1 point by rocketnia 4997 days ago | link

Here's an untested patch to Anarki that should make exceptions more straightforward:

  -(define err error)
  +(define err raise)
   
   [...]
   
   (define (on-err errfn f)
     ((call-with-current-continuation
        (lambda (k)
          (lambda ()
  -         (with-handlers ((exn:fail? (lambda (c)
  -                                      (k (lambda () (errfn c))))))
  -                        (f)))))))
  +         (call-with-exception-handler (lambda (c)
  +                                        (k (lambda () (errfn c))))
  +           f))))))
The idea is that 'raise can raise anything as an exception, even things which aren't an exn:fail? or even an exn?. This should be familiar from JavaScript, and I personally prefer the possibilities of this kind of exception design. ^_^

---

"In case you're wondering why I want to do this, here's the more detailed explanation. I'm writing a function that filters a list (the input) according to another list (the patterns). The default behavior is to throw an error when a pattern doesn't match anything in the input, or when a pattern matches twice, etc."

What happens when there's an actual error?

Not that there's anything horribly wrong with catching exceptions, but it can have odd corner-case consequences. For instance, suppose you don't catch all exceptions from a pattern match, just a specific kind of pattern mismatch exception. That doesn't fix the issue, 'cause we still have to be careful to document which functions can throw pattern mismatch exceptions. If we accidentally let a pattern mismatch propagate from a function as though it's a fatal error, and then we use that function to implement a pattern, then sometimes that pattern may seem to merely mismatch when we expected to see an error.

To avoid cases like this, I recommend encoding the mismatch in the pattern's return value instead.

If that makes pattern-matching code too cumbersome in the simple cases, then I recommend adding an optional parameter in all pattern implementations to specify what should happen in case of a mismatch. By default, it could raise an error. (This would essentially be an ad hoc form of failcall, and Racket also does this in a few places.)

-----

1 point by Pauan 4997 days ago | link

"The idea is that 'raise can raise anything as an exception, even things which aren't an exn:fail? or even an exn?. This should be familiar from JavaScript, and I personally prefer the possibilities of this kind of exception design. ^_^"

Oooh, shiny. That, combined with errsafe returning the exception (rather than nil), should be enough to make exceptions a lot easier to work with.

---

"What happens when there's an actual error?"

Don't care (at least, in this particular program). If you did care, you could pass a function as the second argument to errsafe, and use that to do whatever it is you want to do.

---

"For instance, suppose you don't catch all exceptions from a pattern match, just a specific kind of pattern mismatch exception. That doesn't fix the issue, 'cause we still have to be careful to document which functions can throw pattern mismatch exceptions. If we accidentally let a pattern mismatch propagate from a function as though it's a fatal error, and then we use that function to implement a pattern, then sometimes that pattern may seem to merely mismatch when we expected to see an error."

Not a problem in my case. Patterns are just a list of strings. In a more complicated program, you're right that it's possible that there could be problems.

In any case, that seems like something that should be handled at a higher level[1]... just giving a function as the second parameter to errsafe should be good enough at the low-level.

---

"To avoid cases like this, I recommend encoding the mismatch in the pattern's return value instead."

Huh?

---

"If that makes pattern-matching code too cumbersome in the simple cases, then I recommend adding an optional parameter in all pattern implementations to specify what should happen in case of a mismatch. By default, it could raise an error. (This would essentially be an ad hoc form of failcall, and Racket also does this in a few places.)"

Yeah, that's what I'm doing. By default it raises an error, but you can give it a function, which lets you override the default behavior.

---

* [1]: It'd be interesting to write a macro or something that lets you do different things depending on what type the exception is, ala Python:

  try:
      ...
  except IOError:
      ...
  except (AttributeError, TypeError):
      ...
This could play nicely with prototypes as well.

-----

1 point by rocketnia 4996 days ago | link

"errsafe returning the exception (rather than nil)"

I actually prefer nil. When I use 'errsafe, it's usually in a boolean context like (unless (errafe ...) ...).

---

"Don't care (at least, in this particular program)."

That's just fine. ^_^

"If you did care, you could pass a function as the second argument to errsafe, and use that to do whatever it is you want to do."

I don't really follow, 'cause I don't know your code well enough to know which usage of 'errsafe you're talking about, but no worries.

---

By the way, I do like passing a "default" behavior to 'errsafe. I've often considered doing the same thing myself, but with a somewhat different interface:

  (errsafe a x y z)
  -->
  (on-err (fn (it) x y z) (fn () a))  ; where 'it is anaphoric
This way, if I say (errsafe:err "hey"), I get nil as usual, but if I say (errsafe (err "hey") it), then I get the exception.

Here's another alternative I've thought about, which is a lot like what you're saying with "ala Python":

  (errsafe a x y z)
  -->
  (on-err (fn (it) (if x y z)) (fn () a))
This way, (errsafe:err "hey") and (errsafe (err "hey") it) work exactly the same way, but we can also easily simulate a series of catch/except blocks:

  (errsafe (err "hey")
    an-error-we-expect.it
      (do prn.it (do-something-else))
    an-error-we-dont-want-to-propagate.it
      (err "some better error")
      raise.it)
And here's yet another option I've considered:

  (errsafe)
  -->
  nil
  
  (errsafe a x y z)
  -->
  (on-err (fn (it) (errsafe x y z)) (fn () a))
This way 'errsafe works a lot like 'or, trying several expressions one after another. I don't think I'd ever use this, though. ^_^

---

"Huh?"

By "encoding the mismatch in the pattern's return value," I mean having it return a certain kind of value on a match and another kind of value on a mismatch. For instance, oftentimes I'll return nil on failure and a singleton list on success, so that I can use the idioms (iflet (x) (foo a b c) ...) and (car:foo a b c). However, nil doesn't give much information about the failure, so occasionally I'll return (list t <result>) and (list nil <failure-message>) or (list 'success <result>) and (list 'failure <failure-message>).

Anyway, there's no need for this in your case.

-----

1 point by Pauan 4996 days ago | link

"I actually prefer nil. When I use 'errsafe, it's usually in a boolean context like (unless (errafe ...) ...)."

  (unless (errsafe ... [nil]) ...)
Voila.

---

"I don't really follow, 'cause I don't know your code well enough to know which usage of 'errsafe you're talking about, but no worries."

Right now, you pass 2 functions to on-err, right? The first function receives the exception, if there is any. So what I'm saying is, we could change errsafe so it accepts a second argument, which would behave like the first argument to on-err:

  (errsafe ... (fn (x) ...)) ; the variable x is the exception, if one is thrown
Which is somewhat nicer than doing the same thing with on-err:

  (on-err (fn (x) ...)
          (fn () ...))
---

"This way, if I say (errsafe:err "hey"), I get nil as usual, but if I say (errsafe (err "hey") it), then I get the exception."

Sounds neat.

---

"This way 'errsafe works a lot like 'or, trying several expressions one after another. I don't think I'd ever use this, though. ^_^"

Also sounds neat.

---

"For instance, oftentimes I'll return nil on failure and a singleton list on success, so that I can use the idioms (iflet (x) (foo a b c) ...) and (car:foo a b c). However, nil doesn't give much information about the failure, so occasionally I'll return (list t <result>) and (list nil <failure-message>) or (list 'success <result>) and (list 'failure <failure-message>)."

Sounds really ad-hoc. :P If I'm understanding correctly, that sounds like a manual version of failcall.

-----

1 point by rocketnia 4996 days ago | link

Oh yeah, there's something I think would be even more useful than any of these 'errsafe variants:

  (ifreturn result exn (foo)
      do-something-with.result
    an-error-we-expect.exn
      (do prn.exn (do-something-else))
    an-error-we-dont-want-to-propagate.exn
      (err "some better error")
      raise.exn)
  
  (aifreturn (foo)
      do-something-with.it
    an-error-we-expect.it
      (do prn.it (do-something-else))
    an-error-we-dont-want-to-propagate.it
      (err "some better error")
      raise.it)
With a traditional try block, if I want to 'do-something-with the return value only if there isn't an error, the obvious place to do it is right there inside the try block, after the value has been successfully calculated. However, that means the errors thrown by 'do-something-with itself can be caught, which is almost never my intention.

Instead, I go to great lengths with booleans just to avoid doing putting too much inside the try block. What I really want are macros like these.

Meanwhile, the same idea could be beneficial for fixed-syntax languages too:

  try:
      foo()
  then result:
      do_something_with(result)
  except Exception as e
      do_something_else_with(e)
  
  try
  {
      foo();
  }
  then ( Foo result )
  {
      doSomethingWith( result );
  }
  catch ( Exception e )
  {
      doSomethingElseWith( e );
  }

-----

1 point by aw 4995 days ago | link

I finally remembered this morning that Arc already does have a mechanism for passing a value up through multiple layers of code execution by using continuations: point (and the derived macro catch/throw).

point can be used in the kind of situation that you might want to use Racket's raise for. In fact, you can think of point as a macro that creates a "raise" function.

Python doesn't have continuations, so the only way to break out of multiple layers of function calls at once is by catching and throwing an exception.

There are situations where something happens in a program that we expect to happen (at least sometimes), and we need to stop doing what we're currently doing and start doing something else, and often pass some data along that we need.

However, I noticed in production code written in Python where throwing and catching an exception is used to implement this, it's not uncommon for the implementation to also catch other exceptions that the programmer didn't expect... leading to real errors arising from bugs not being reported.

So I think for this scenario at least, using point/catch/throw is a better mechanism because it's explicitly only catching the things you plan to throw, and thus leaves handling unexpected conditions (things you didn't plan for, and so are actual bugs in your program) to the error handling mechanism, where you want debugging information like the source code line location and so on.

Which in turns leads me to suspect that perhaps errsafe is the wrong choice for handling errors opening a file... but that would be the subject of a different post :)

-----

1 point by Pauan 4995 days ago | link

Yes, I agree with you. catch/throw does seem better than using exceptions for everything. Python's infatuation with exceptions doesn't seem like the best course of action.

However, that does raise one question: should we have exceptions at all?

Note: I do actually want to use `err` in my program, because I want all execution to halt. I just want it to halt after all the errors are finished, rather than after only one error.

Perhaps I should use (quit) instead... does that automatically close any files opened by Arc?

-----

2 points by aw 4995 days ago | link

Perhaps I should use (quit) instead... does that automatically close any files opened by Arc?

Yes. quit exits the process, and the operating system closes all open files when the process terminates.

However, that does raise one question: should we have exceptions at all?

We still do want a mechanism for reporting actual program errors. Things that are truly bugs. For example, a division by zero exception is always avoidable by not passing 0 as the divisor. I dislike using a try-catch mechanism in my program for checking for things like dividing by zero, because I end up catching other cases of dividing by zero that I didn't intend to, which are actual bugs. So if I expect I might have 0 as a divisor, I prefer to check for it explicitly before calling /. Thus (when I write programs the way I like to), any actual division by zero exceptions that get thrown are in fact actual bugs. Which I then want to capture and report with lots and lots and lots of debugging information.

-----

1 point by aw 4997 days ago | link

Aside from using raise which probably would be better anyway, I wonder if there would be any reason not to have on-err pass the "details" (the exn-message) of the exception object to the on-err error handler, instead of the exception object itself.

-----

1 point by rocketnia 4997 days ago | link

I think the message can only be a string, which makes it a bit difficult to do interesting things in the handler.

-----

1 point by aw 4997 days ago | link

yes, right now in Arc 3.1 if I catch (err "foo") with on-err, I have to use details to extract the string "foo", but with your idea of using raise we'd get the string "foo".

So in this way your idea and my idea would work out the same: both would pass the string "foo" to the handler when (err "foo") was used.

A difference would be primitive errors generated by Racket, with my approach the handler would get the string like "/: division by zero" while with your implementation the handler would still get the exception object.

I don't think we'd want it to matter if errors are generated internally by Arc or Racket, so assuming that we do want to continue to raise errors in Arc with (err "foo") then I'm guessing we'd want to special case Racket exceptions and pass the exn-message string to the handler so that it would end up working the same way.

By the way I don't think you do want to use call-with-exception-handler; if the handler returns then then Racket invokes the next exception handler in the chain, so it's like re-raising the exception. But the with-handlers form works fine with arbitrary values raised by raise.

-----

1 point by rocketnia 4996 days ago | link

"I don't think we'd want it to matter if errors are generated internally by Arc or Racket, so assuming that we do want to continue to raise errors in Arc with (err "foo") then I'm guessing we'd want to special case Racket exceptions and pass the exn-message string to the handler so that it would end up working the same way."

That actually makes me wonder if Arc should have access to 'raise as a separate function from 'err. After all, if we catch a Racket exception as a string, we lose information.

For all I know, losing that information isn't so bad. I almost always design code so it never throws exceptions that should be caught, so I don't know what information is useful to have in an exception. I've only encountered two places it makes sense to capture an exception:

- When the exception implements an escape continuation, in which case the necessary information is the unique identity of the catch point.

- When the program is something like a REPL or server that handles multiple commands and should typically recover from an error encountered in any one command. In this case, the necessary information is whether the error should be considered severe enough to take down or restart the particular command loop in progress. The usual approach to representing this information is a hierarchy of types such that, if you say exn:fail is okay, you implicitly say exn:fail:contract:divide-by-zero is okay. Is there a better approach?

---

By the way I don't think you do want to use call-with-exception-handler; if the handler returns[...]

That handler doesn't return, but good point.

Reading over the reference some more (http://docs.racket-lang.org/reference/exns.html), it looks like there are some potential things to consider with breaks:

"...the exn:break exception typically should not be caught..."

"Before any predicate or handler procedure is invoked, the continuation of the entire with-handlers expression is restored, but also parameterize-breaked to disable breaks."

Would it be better or worse to switch to 'with-handlers*, which doesn't disable breaks?

-----