Getting Out Of ENTRAP-ment

When antiform ERROR! first arrived, there needed to be a way to capture them.

I was uncomfortable having the LIFT operator blindly turn an antiform ERROR! into a quasi-ERROR!, because that could sweep things under the rug. You might have been LIFT-ing because you wanted to capture a PACK! (including VOID) or so you could put a NULL or SPLICE! into a BLOCK!. Why would you conflate that with error suppression?

So there was LIFT that didn't meta an ERROR! antiform, and LIFT* that would.

Now you want to capture an error from an operation. You've got a situation like:

result': lift* eval code

if error? unlift result' [
     let warning: unquasi result'
     ; ... handle disarmed error (warning) ...
] else [
    let result: unlift result'
    ; ... handle result ...
]

I thought this was a bit more painful than it needed to be, so I created ENTRAP.

result': entrap code

if warning? result' [
     ; ... handle disarmed error (warning) in result' ...
] else [
    let result: unlift result'
    ; ... handle result ...
]

Not a heck of a lot better. But saved a bit of boilerplate.

Now We Can Do Better!

(^result: ...) will write the lifted representation of the evaluation into result, but still return the original result. So if the original result was an ERROR! it will still be an error out.

But you can suppress that. So look at this magic:

try ^result: eval code  ; TRY suppresses, but meta-ERROR! still wrote to result

if error? ^result [
     ; ... handle error in ^result ...
] else [
    ; ... handle original result as ^result ...
]

:bullseye:

So What, Then, Is TRAP Now?

Right now EVAL only lets you bubble out an error if it's the last step. Interstitial ERROR! will lead to a PANIC if there's no triage:

>> error? eval [print "step one" 1 / 0]
step one
== \~okay~\  ; antiform

>> error? eval [1 / 0 print "step two"]
!! PANIC: division by zero

That's kind of a non-negotiable default, because when you branch and pipe ERROR! values around you intend for the error to be the product of the last branch step, not some incidental code that ran before that last step.

So maybe TRAP is a construct that lets you bail early, getting an error from any step:

>> error? trap [print "step one" 1 / 0]
step one
== \~okay~\  ; antiform

>> error? trap [1 / 0 print "step two"]
== \~okay~\  ; antiform

Or maybe that's a refinement to EVAL. I dunno.

Another thing to point out is that ERROR! is not an exception (panic). It's a FAIL state that can be piped around. So using EXCEPT as name for the postfix error handler is a little off:

 eval code except ^e -> [
    ; handle ERROR!
 ]

Should that be TRAP ?

 eval code trap ^e -> [
    ; handle ERROR!
 ]

Food for thought. In any case, I'm really pleased to see how (^x: ...) vs. (try ^x: ...) offers the choices I was seeking, in so much cleaner a way.

1 Like

I should point out that this is probably still a good idea.

It's true that you'll be able to put things in blocks by doing things like:

block.^1: eval [...]

Instead of:

block.1: lift eval [...]

And you'd be sort-of protected in that case, because if you don't TRY the result the ERROR! would still fall out the assignment. (I say "sort-of" because you find out only after it's done the change, and written the meta-error into your block).

But then there's cases like:

append [a b c] lift eval [...]

And there you're back where you started, with putting lifted-errors in blocks vs. panicking at that moment when you probably wanted to.

So needing lift:relax in order to be willing to meta-represent errors, and shorthanded as lift*, feels like it makes a lot of sense.

(Do note that REIFY and DEGRADE exist if quasiforms are out of band of your data, and has the nice property of just making little quasiforms for your voids and nulls and splices and such, without adding quote levels to normal values. There is no REIFY:RELAX at this time, though perhaps there should be.)

This "not stopping the write" aspect is actually a pretty serious fundamental thing to be taken into account for using this form.

It suggests you should never use foo.^xxx: unless you are intending to write errors, or unless you've made sure you're not writing errors some other way.

foo.^xxx: eval code except ^e -> [...]  ; not writing error

Though that calls into question the "what if an EXCEPT block evaluates to a raised error" issue, which maybe should be prevented, at least by default--unless you use EXCEPT:RELAX or something. :face_with_diagonal_mouth:

Anyway, I just wanted to bring up this caution, that writing (foo.^xxx: ...) is a power tool that needs to be used carefully, since it never fails to overwrite the variable.