R3-Alpha had an idea--carried forward by Red--of an arity-1 IF combinator.
red>> num: 1020
red>> parse [a a a] [if (even? num) some 'a]
== true
red>> parse [a a a] [if (odd? num) some 'a]
== false
As you see, if the expression you give it turns out to be "falsey" then it doesn't continue matching. It skips to the next alternate--if there is one.
red>> parse [a a a] [if (odd? num) some 'b | some 'a]
== true
But I always thought the arity-1 IF was a pretty alien thing that would confuse people. You might think there's a branch, but there's no "branch"... just continuing along with the variadic list of everything that follows until the next | or end of BLOCK!.
I also wondered "where does it end?" With an IF combinator, why not a CASE combinator, or SWITCH combinator?
So when I came up with GET-GROUP! doing arbitrary substitutions of the rule it evaluates to, I thought "hey, that's a lot more general!" We could just say that ~true~ and ~void~ antiforms would continue the parse, ~false~ would stop it, and ~null~ antiforms would trigger an error in case you didn't mean to do that.
What That :(GET-GROUP!)
Concept Looked Like
(Note that if condition '[...] is equivalent to if condition [[...]]. This is called "soft-quoted branching")
>> num: 1020, rule: null
; generated [some 'b] rule is treated as if it had been written there
>> parse [a a a b b b] [some 'a :(if even? num '[some 'b])]
== b
; generated ~void~ from non-taken IF gets ignored, and it kept parsing
>> parse [a a a b b b] [some 'a :(if odd? num '[some 'c]) some 'b]
== b
; generated ~true~ signal continues parse, just as ~void~ did
>> parse [a a a b b b] [some 'a :(even? num) some 'b]
== b
; generated ~false~ skips to next alternate (isn't one, so parse fails)
>> parse [a a a b b b] [some 'a :(odd? num) some 'b]
** Error: PARSE BLOCK! combinator did not match input
; treat ~null~ conservatively, use :(maybe rule) for ~void~ to keep going
>> parse [a a a b b b] [some 'a :(rule) some 'b]
** Error: ~null~ antiform generated by GET-GROUP! in PARSE
Flexible Logic Kills [~true~ ~false~]
... Breaks That Idea
In the flexible logic model, [TRUE FALSE ON OFF YES NO]
are WORD!s, and hence indiscriminately trigger taking the branch in something like an IF when used directly. The ~null~ antiform is the "branch inhibitor", and it's what conditional expressions return when they don't match the condition.
>> 10 > 20
== ~null~ ; anti
I don't think it's a good idea to make substitions via GET-GROUP! (or whatever comes to replace it) silently continue on NULL. If you forgot to set a variable that was supposed to hold something (as in rule above), that should give you an error. But I don't think you should have to write :(maybe even? num)
So Having A Conditional Logic Combinator Makes Sense
I just think that IF is a rather lousy name for it.
So I'll suggest WHEN.
>> parse [a a a b b b] [some 'a, when (even? num), some 'b]
== b
It would be against the premise of flexible logic to have WHEN be biased and assume things like TRUE, YES, or NO should mean it continues or not. I like the idea that you could hold a completely arbitrary word in a variable and say when (word)
, that means "continue matching when word is set to a non-null value".
Hence you'd have to say when (true? flag)
or when (off? toggle)
etc. I'm not merely comfortable with this... I am gung-ho about it!
(Of course people can make their own combinators and build in biases of their choosing, the core just doesn't pick sides.)
BYPASS Can Be A Synonym For [when (null)]
I didn't like using FAIL for saying when to stop a rule chain and go to the next alternate, because that is used for causing "abrupt failures" in the system.
So I'd been using quasiform ~false~
the state in source (and the antiform if in a variable).
>> parse [a a a b b b] [some 'a, :(if even? num [false]), some 'b]
** Error: PARSE BLOCK! combinator did not match input
>> parse [a a a b b b] [some 'a, ~false~, some 'b]
** Error: PARSE BLOCK! combinator did not match input
But that isn't the model anymore. There is no ~false~ or ~true~ antiform. And honestly it wasn't that literate anyway. when (...) makes it clearer when you're using a variable. And the quasiform just looks confusing.
Searching for a good word that doesn't run into something serving other purposes (e.g. BREAK), I asked Claude.ai for suggestions, and one of those was BYPASS.
I like it. So for example you could write:
>> parse [a a a b b b] [some 'a [:(if even? num ['bypass]) some 'c] | some 'b]]
== b
Although that particular case is clearer as [when (odd? num) ...]
, but sometimes you have to throw in a bypass rule.
(Amusingly, in Rebol2 the idiom for BYPASS was [end skip]
, which was a rule guaranteed to mismatch at any position: either you weren't at the tail and the END wouldn't match, or you were at the tail and the END would match but then you couldn't SKIP.)
Where Does It Stop?
I also wondered "where does it end?" With an IF combinator, why not a CASE combinator, or SWITCH combinator?
So I think it's good to just say WHEN.
You don't technically need WHEN if you have BYPASS to skip to next alternate, and ~void~ to keep going (or empty block, if you like... []
will keep going too).
when (cond) => :(if not cond ['bypass]) ; or :(if not cond 'bypass)
But that forces you to reverse the sense of your logic and write out something longer (and slower). I think if you've got logic that's complex like a case or switch, then writing it out as a splicing rule would have negligible benefit to try and shoehorn as a combinator.