Pointing the Blame In FAIL

If you haven't been using FAIL, you should! I've pointed out in the past you can just say case [... fail] and that's enough to get a failure and point to that location. But it now lets you just point at any arbitrary value via a quoted word and it will complain about that word and its value:

>> foo: 10

>> fail 'foo
** Error: foo is invalid: 10
** Where: fail console
** Near: [fail 'foo ~~]

It works for tuples too:

>> obj: make object! [field: <I've been a bad, bad field...>]

>> fail 'obj.field
** Error: obj.field is invalid: <I've been a bad, bad field...>
** Where: fail console
** Near: [fail 'obj.field ~~]

So this should be your lazy go-to for reporting errors, you get more information and type less than using PRINTs or longer-winded messages.

But the real strength is implicating callsites, vs FAILs

If you don't provide a place to blame, the only thing the evaluator knows is the whole stack and the place the failure was.

 my-api: func [x y z] [
     if true [
         if y < 100 [
              fail "Who's failing?  one of those IFs?  Or MY-API?"
        ]
     ]
 ]

>> my-api 1 2 3
** Error: Who's failing?  one of those IFs?  Or MY-API?
** Where: fail if if my-api console
** Near: [
    fail "Who's failing?  one of those IFs?  Or MY-API?" ~...

That's not a terribly informative error message, but the evaluator doesn't know any more unless you tell it.

This is a job for FAIL/BLAME! Just pass it a WORD! from the frame you wanted to complain about.

my-api: func [x y z] [
     if true [
         if y < 100 [
              fail/blame ["Value must be >= 100, not" y] 'y
        ]
     ]
 ]

>> my-api 1 2 3
** Error: Value must be >= 100, not 2
** Where: my-api console
** Near: [... 1 2 3 ~~]

Now the IFs are gone from the reporting stack, and you're indicating the callsite but not the FAIL itself. (Ideally the error message would point to the exact block position where the parameter you are complaining about originated from. But that information is not preserved at the moment. In the future it might be kept...maybe in a special slot that would go unused otherwise in 64-bit builds. So the error report would improve!)

Again, you could be lazy and just say fail 'y and get a fair amount of information. In this case a bit of better information than in the custom message provided--in terms of the right name of the bad parameter!

This isn't arcane C magic, it's usermode Rebol!

You can read and help improve FAIL any day you feel like it! Please do!

1 Like

Sweet. Error handling, one of my bugaboos with rebol. This is a nice improvement and should encourage better programming practice.

5 posts were split to a new topic: Specificity of Error IDs (PATH! => URL!)

With the advent of CHAIN!s in the Big Alien Proposal For Function Calls, we have a way to push the /BLAME argument to be in front of the error...instead of behind it.

Let's say we use CHAIN! like this:

my-api: func [x y z] [
     if true [
         if y < 100 [
             fail:$y ["Value must be >= 100, not" y]
        ]
     ]
 ]

This is all speculative in terms of exactly how the function would blend its refinement processing with dialecting the CHAIN! it was invoked with. But using fail:$y instead of fail:y pushes the variable out of band of other refinements that might be used... e.g. you know it's not trying to use a refinement named "y"

I actually think it looks better than fail $y [...]. Or rather, coheres better. It makes more sense when you look at it, I think. It's the same number of characters, but it makes it pretty clear that the 1st argument to the FAIL is consistently the error specification. It's in the right slot.

Note we can't use 'y to point blame at a variable, because that has no binding.

But I can't really tell whether we should be using @y or $y.

; supplying an argument for the FAIL's "BLAME"

fail/blame ["Value must be >= 100, not" y] $y
fail:$y ["Value must be >= 100, not" y]  ; shorthand

fail/blame ["Value must be >= 100, not" y] @y
fail:@y ["Value must be >= 100, not" y]  ; shorthand

I'll point out that it will also be possible to use a variable as the primary argument, so an answer for the semantics of that ties in as well.

; supplying an agument for the FAIL's "REASON"

fail $y

fail @y

The CHAIN! interpretation is up to the function. But the evaluator dictates the behavior of the case when passing as an argument. And if you use the $y form, then FAIL receives a plain bound word... but the @y form will give a bound @y with the decoration still intact.

I think the $ looks slightly better. Beyond that, I'm not sure what guides this particular decision.

Could (Should?) They Mean Different Things?

The (fail @y) form could report a direct complaint about a value.

 >> y: 10
 >> fail 10
 ** Error: FAIL does not accept INTEGER! as its REASON argument
 >> fail @y
 ** Error: Invalid Value for y: 10

 >> y: "Some String"
 >> fail y
 ** Error: Some String  ; acts same as (fail "Some String")
 >> fail @y
 ** Error: Invalid Value for y: "Some String"

Then perhaps (fail $y) could be interpreted in more of a sense of blame, without saying that the value is particularly problematic...just pointing to the stack level for the error. This makes it a shorthand for:

fail/blame [] $y
; or
fail:$y []

I think I like this axis of distinction. So it suggests /BLAME should take (bound) WORD!s, and hence the $y form is correct. Works out nicely that it looks better...