In traditional Redbol, if you wrote a random chain like like first select block second options, there was the question of how you would manage the situation of any of these things failing.
People would request lenience... to say more operations should assume that if they got a NONE! input that they should just return a NONE! output. But this would give those chains no error locality... you'd get a NONE! at the output and not know what failed. The FIRST? The SELECT? The SECOND...?
You can see DocKimbel's response to a request that INDEX? NONE be NONE:
meijeru
"There are precedents for built-in functions on series which yield none for none."DocKimbel
"Yes, but the less of them we have, the better, as they lower the robustness of user code, by making some error cases passing silently. The goal of such none-transparency is to be able to chain calls and do nothing in case ofnone, avoiding an extraeitherconstruct. In the above case, it still requires an extra conditional construct (any), so that is not the same use-case as for other none-transparent functions (likeremove)."
But I Didn't Give Up So Easily...
The strategy cooked up for Ren-C is called "VETO-in-NULL-out".
There is an asymmetry created, in which if a function receives the "hot potato" unstable antiform ~(veto)~, then it will return NULL after it has typechecked its other arguments:
>> foo: lambda [x [integer!] y [integer!]] [x + y]
>> foo 1000 20
== 1020
>> foo ~(veto)~ 20
== \~null~\ ; antiform
>> foo ~(veto)~ "not an integer"
** PANIC: foo got ~{text!}~ for y argument, expected [integer!]
Then COND can turn nulls into VETO for you, passing through other values
>> num: 1000
>> foo (cond num) 20
== 1020
>> num: null
>> foo (cond num) 20
== \~null~\ ; antiform
Since few functions naturally takes NULL as an input, this creates a dynamic where you'd put as many COND in a chain as you felt was warranted, if you expected any steps could fail. So perhaps first cond select block cond second options. A reader could tell which operations could potentially fail using this method.
It has shown systemic success:
>> case [null [[a [b] c]]]
== \~null~\ ; antiform
>> second case [null [[a [b] c]]]
** PANIC: second got ~null~ for value argument, expected [any-series?]
>> cond case [null [[a [b] c]]]
== \~(veto)~\ ; antiform (pack!)
>> second opt case [null [[a [b] c]]]
== \~null~\ ; antiform
>> first second cond case [null [[a [b] c]]]
** PANIC: first got ~null~ for value argument, expected [any-series?]
>> first opt second opt case [null [[a [b] c]]]
== \~null~\ ; antiform
>> first opt second opt case [okay [[a [b] c]]]
== b
If You Actually Want ~(veto)~ Args, Use [<veto>]
While the default behavior for receiving veto is to bypass the function and return null, a very small minority of functions actually want to receive ~(veto)~ antiforms as an argument.
One obvious case of this would be the VETO? function for testing if something is a veto. But also, PACK? wants to report that it is--indeed--a PACK!. The TRY function defuses vetoes at the moment, and so it's another case that inspects it.
To receive it, mark your argument with [<veto>].
>> vtest: proc [^arg [<veto> any-value?]] [
if veto? ^arg [print "I got a veto!"]
]
>> vtest veto
I got a veto!
VETO (and COND) Can Be Used Lots of Places!
You'll find that VETO-awareness comes up many places, to let you short-circuit an operation.
For instance, REDUCE:
>> reduce [1000 + 20, if 1 < 2 [veto], 300 + 4]
== \~null~\ ; antiform
>> item: <thing>
>> reduce [1000 + 20, cond item, 300 + 4]
== [1020 <thing> 304]
>> item: null
>> reduce [1000 + 20, cond item, 300 + 4]
== \~null~\ ; antiform
You can similarly opt out of slots in COMPOSE:
>> compose [1020 (veto) 304]
== \~null~\ ; antiform
But It's Very Powerful In PARSE!
While the results of a GROUP! are typically ignored after being evaluated in PARSE, a VETO is special... so if a group evaluates to a VETO it goes to the next match rule!
>> parse "aaa" [(if 1 = 1 [veto]) some "a" | some "a" (print "hello")]
hello
== "a"
This gives you great flexibility for sending the PARSE a signal from inside the group, without using the INLINE combinator. Having the GROUP! decide "no, I don't want to match, skip to the next parse alternative" is probably the most common signal a group would want to send!