4

I'm a bit confused over error handling in Rebol. It has THROW and CATCH constructions:

>> repeat x 10 [if x = 9 [throw "Nine!"]]
** Throw error: no catch for throw: make error! 2

>> catch [repeat x 10 [if x = 9 [throw "Nine!"]]]
== "Nine!"

But this throwing and catching is unrelated to the handler passed to the /EXCEPT refinement on TRY:

>> try/except [throw "Nine!"] [print "Exception handled!"]
** Throw error: no catch for throw: make error! 2

That's very specific to Rebol's error type:

>> try/except [print 1 / 0] [print "Error handled!"]
Error handled!

If you want you can trigger your own errors, but not using THROW as you would in other languages. Throwing errors will just lead to a complaint about not being caught, as any other value type:

>> try/except [throw make error! "Error string"] [print "Error handled!"]
** Throw error: no catch for throw: make error! 2

You must DO them to get the evaluator to try and execute something of type error! to cause what it considers an "exception":

>> try/except [do make error! "Error string"] [print "Error handled!"]
Error handled!

(Note: You can use pre-made errors apparently such as cause-error 'Script 'invalid-type function! -- see system/catalog/errors for more.)

Leaving off the /EXCEPT refinement will let you receive any errors as a value. However, that seems to make it impossible to distinguish between whether the error was invoked or not:

>> probe try [do make error! "Some error"]
make error! [
    code: 800
    type: 'User
    id: 'message
    arg1: "some error"
    arg2: none
    arg3: none
    near: none
    where: none
]
** User error: "Some error"

>> probe try [make error! "Some error"]
make error! [
    code: 800
    type: 'User
    id: 'message
    arg1: "Some error"
    arg2: none
    arg3: none
    near: none
    where: none
]
** User error: "Some error"

It would seem that CATCH has a similar lack of distinction between returning a value and having a value thrown. But there's a tool to get around that with "named throws":

>> code: [repeat x 10 [if x = 9 [throw/name "Nine!" 'number]]]

>> catch/name [do code] 'number
== "Nine!"

>> catch/name [do code] 'somethingelse
** Throw error: no catch for throw: make error! 2

So now for the questions:

  • Is there a practical value to this separation? How does one decide whether to use THROW and CATCH or a DO of an error and handle it with TRY/EXCEPT?

  • Does this distinction have precedent in other languages, and would /EXCEPT be better named /ON-ERROR or something?

  • Why does it say "no catch for throw: make error! 2" instead of something more informative? What is the make error! 2?

Community
  • 1
  • 1

3 Answers3

5

"Is there a practical value to this separation? How does one decide whether to use THROW and CATCH or a DO of an error and handle it with TRY/EXCEPT?"

As has been pointed out in the other answers as well, THROW and CATCH form an unwinding construct (a non-local exit) that in and of itself is concerned with control flow and doesn't necessarily have anything to do with error handling. THROW and CATCH is first and foremost a straightforward method to influence control flow and is a rather fundamental building block for other custom control flow constructs.

As such, THROW and CATCH can of course also be used for building an "exception handling"-like error system as seen in many contemporary mainstream languages, because those "exception handling" systems are one instance of non-local control flow.

Rebol's error! on the other hand is the primary method to signal and propagate evaluation errors.

So generally, the decision is easy to make: if you want to cause an error, use error!. If you want to influence control flow to unwind controllably, use THROW / CATCH.

Two more remarks regarding terminology:

  • Discussion around Rebol errors has become more careful to use "cause an error" as a phrase instead of "throw an error", to avoid confusion.
  • In a similar vein, references that call THROW / CATCH "Rebol's exception handling" (as @HostileFork alludes to in one comment) need to be reworked.

"Does this distinction have precedent in other languages"

Yes, the distinction between evaluation errors and non-local exits has precedent in other languages, especially the Lisp family. A few quick references:

"Why does it say "no catch for throw: make error! 2" instead of something more informative? What is the make error! 2?"

That's a bug. (Good catch!) I'd say that the core of the error message ("no catch for throw") is rather informative already, but the make error! 2 is a bug (it should rather show the thrown value here.)

"would /EXCEPT be better named /ON-ERROR or something?"

Renaming /EXCEPT is debatable. As such, I'd say this is a discussion not that suitable for SO Q&A and better left to other fora.

earl
  • 40,327
  • 6
  • 58
  • 59
2

I would say that the regular throw is not for error handling, but for shortcuts or goto. It serves something as a break in loops etc. You could use it also to mimic a continue

See is-there-an-equivalent-to-continue

Community
  • 1
  • 1
sqlab
  • 6,412
  • 1
  • 14
  • 29
  • A definition of exception handling that sounds reasonable to me is: *"an exception is when a member fails to complete the task it is supposed to perform as indicated by its name."* By that definition, I would say that there needs to be another category for THROW/CATCH; perhaps call it "Rebol's unwinding operations" vs. "Rebol's exception handling"... – HostileFork says dont trust SE Jun 25 '14 at 16:40
2

I am not very familiar with error handling in Rebol, but the point about exceptions and throwing and catching is that they are often thought of as "error handling" constructs, which is a rather narrow concept. Instead, it is better to think of try/catch as a flow control mechanism. But whereas exiting a loop just exits an immediate surrounding block, the benefit you get from throwing an exception is that it can straddle a number of frames in the stack. You can therefore isolate individual blocks of code more easily as a logical unit, which, if any part of it fails, can be treated as a failure of the logical unit, regardless of how far down in the stack the exception occurs.

Sorry, that did maybe not answer your question directly, but I thought I could address your second point: whether it should be called ON-ERROR. I would say no, because an exception does not necessarily imply an error; as a means of flow control it is just a more general instruction that a condition occurred that shouldn't allow further execution down a specific path.

mydoghasworms
  • 18,233
  • 11
  • 61
  • 95
  • I mentioned in another comment that I liked the definition *"an exception is when a member fails to complete the task it is supposed to perform as indicated by its name."* Anyway, I feel the terminology needs to get sorted out here... because I had been doing a lot of `throw make error! "..."` because I thought that was what you were supposed to do, and took it upon myself to then parse out the error object to print and quit... not realizing I could DO the error and get the same behavior. It is unlike other languages, and should be explained more clearly. – HostileFork says dont trust SE Jun 25 '14 at 19:19