TRIPWIRE in The Wild

If you access an unset variable, you don't get a lot of information back:

>> state: ~

>> state
** Script Error: state is ~ antiform
** See ^(...) and GET/ANY

But with antiform tags, you can embed a message into the unset variable:

>> state: ~<INITIALIZE-STATE hasn't been called yet>~

>> state
** Script Error: state is ~<INITIALIZE-STATE hasn't been called yet>~ antiform
** See ^(...) and GET/ANY

This new concept is called a TRIPWIRE.

DEFAULT reacts to it, considering the variable to be "vacant"

>> vacancy? get/any $state
== ~okay~  ; anti

>> state: default [10 + 20]
== 30

>> state
== 30

Can Be Better Than A Meandering Comment!

A comment won't direct people to the relevant issue at runtime. But a well-chosen tripwire can.

Compare:

; !!! UPARSE is not available in SYS because it is higher level.  We hack it
; up so that when %uparse.r runs it pokes itself into sys.util/parse
;
parse: ~

With:

parse: ~<higher-level %uparse.r hasn't set SYS.UTIL/PARSE yet>~

Much better! Gives you the information you need, when you need it!

(Note: I was inspired to this concept by a style of usage @rgchris had in his scripts, using TAG!s to provide guidance for what was supposed to be filled into certain slots. I just wanted to give that some teeth, to know that the variable was conceptually unset.)

2 Likes

Is Signaling With Tripwires Good Or Bad?

I don't totally remember the motivation, but I came up with a feature of ASSERT where you could give it a handler...to receive the assertion condition that failed, and process it in some way:

>> h: func [cond] [print ["condition is:" @cond]]

>> assert:handler [1 = 1, 2 = 3, 3 = 4] get $h
condition is [2 = 3]
** Error: Assertion Failure....

Maybe it's interesting. I dunno. But something I had in it was that ASSERT would check the return value from the function, and if it was an ~ignore~ WORD! antiform it would not raise an error, but would continue processing the conditions.

We don't allow arbitrary keywords any longer, but the feature could still be approximated with a tripwire as ~<ignore>~.

>> h: func [cond] [print ["condition is:" @cond] return ~<ignore>~]

>> assert:handler [1 = 1, 2 = 3, 3 = 4] get $h
condition is [2 = 3]
condition is [3 = 4]

>>

There have been some changes to where functions return nothing by default, if you don't have a return statement. But this doesn't apply to lambdas.

So some of the idea here is to let you use a thing like a lambda and have a weird/ornery out-of-band kind of result that signals a behavior, while other values (blocks, integers, anything you might randomly synthesize) won't. e.g. h: cond -> [append log cond] as a handler would log the assert but still fail, while h: cond -> [append log cond, ~<ignore>~] would log and not fail.

Obviously there's potential for failure or conflation if you call a function that just so happens to evaluate to ~<ignore>~ and you didn't mean to ignore. But eh. It is what it is, and it's kind of cool.

Making this kind of thing somewhat tractable is the fact that tripwires allow comparisons (while nothings don't). So they are more readily used in this way, despite being "ornery".

I'll keep this around for now.

Tripwires Catch Bugs Failing Functions Won't

Prior to the existence of tripwires, there were some functions in LIB for things like RETURN, CONTINUE, THROW, etc. to give you messages when you tried to use these definitional constructs in places that didn't provide them:

/return: func [] [
    fail:blame "RETURN called when no generator is providing it" $return
]

/continue: func [] [
    fail:blame "CONTINUE called when no loop is providing it" $return
]

/throw: func [] [
    fail:blame "THROW called when no catch is providing it" $return
]

...

Passing the $return bound word as the :BLAME argument is a bit awkward to write. But it means the error will blame the callsite, showing the error there--instead of implicating the FAIL itself inside the stub function. That makes the error much more useful.

But this still makes it look like the functions are available. :frowning:

You won't get an error if you say return/ to try and get a RETURN function. You'll get a function--and it will pass the rule that things ending in slash must look up to functions--but it's not the kind of RETURN function you actually wanted.

This led to confusing downstream bugs when some code wasn't binding correctly, but proceeded merrily along as if it had gotten a RETURN function.

Tripwires make it nice and tidy!

return: ~<RETURN used when no function generator is providing it>~

continue: ~<CONTINUE used when no loop is providing it>~

throw: ~<THROW used when no catch is providing it>~

...

You get the "blame" of the callsite for free, because tripwires aren't function instantiations.

And if you say return/ then that's an error, because it's not a function.

This is quite an improvement!

2 Likes