#1

(For those culturally unaware of the reference: DoubleMint gum commercials)

Doubled-up blocks are distinct to the evaluator from single ones. if true [[stuff]] acts different from if true [stuff].

This is not the case for doubled-up groups. ((1 + 2)) will act the same as (1 + 2)…because if you’ve got a single group inside of a group, there’s nothing for it to interact with.

So rather than let them sit around being useless, why not use these cool-looking things for some practical purposes? Here are two nifty new uses…

## Double Groups in COMPOSE mean /ONLY for that insertion

``````>> data [a b c]

>> compose [plain: (data) only: ((data))]
== [plain: a b c only: [a b c]]
``````

Boom. The bulkier look alludes to the insertion being “heavier” and more isolated.

For the cost-conscious, it might look like you’ve got an extra series now. But Ren-C has some hard-core efficiency tricks that make such doublings very efficient, through what is called the “small series optimization”. And you got rid of the COMPOSE/ONLY 2-element path…which at 2 elements long it doesn’t qualify for the small series optimization. Plus refinement processing costs more in the evaluator than dispatching through a plain word.

It’s not just more convenient and compact, but more efficient.

## Double Groups in PARSE mean “execute and splice as rule”

There’s efficiency to be gained when you compose your parse rules ahead of time. But what if the rule depends on other things, and you want to generate a rule to splice in that gets composed in every time it’s run?

``````>> num: 1
>> char: #"a"
>> did parse ["a" "bb" "ccc"] [
some [
into [((
rule: reduce [num char]
num: num + 1
char: char + 1
rule
))]
] end
]
== #[true]
``````

(Note: You shouldn’t need the block outside the doubled group for the INTO, but at the moment INTO isn’t aware of these doubled groups in its immediate argument…so they have to go in a block rule. It will be updated to recognize them directly.)

If the expression inside the double group evaluates to a null, it splices nothing into the rule stream. With so many null-signaling routines to choose from, that’s rather cool.

``````>> did parse "aaabbbcccddd" [
some "a"
((case [1 > 2 [[some "x"]] 4 > 3 [[some "b"]]]))
some "c"
((switch 1 + 2 [
4 [[some "z"]] 3 + 2 [[some "q"]]
]))
some "d"
end
]
== #[true]
``````

Though you can return BLOCK! rules to splice, you can also just return single WORD!s. At the moment, returning a BAR! is disabled, though that might be relaxed based on feedback.

The fact that the blocks are not spliced is conveyed nicely in parity with the /ONLY-ness character of doubles in COMPOSE.

## Pretty neat, huh?

There are some other cool compose features forthcoming that aren’t ready yet, but this kicks it off with a very usable thing you can get started with right now. It seems to me that nearly every COMPOSE/ONLY I’ve seen is better off with the doubled groups, as only some things need the /ONLY and it helps call out the ones that do.

2018 Retrospective: Elevating the Art
#2

Love both of these features. Definitely useful immediately for me.

#3

Two PARSE cases to consider:

``````single: quote ([some "b"])
double: quote (([some "c"]))
parse "bbbccc" [some [(single) | double]]
``````

#4

The (single) there would be a no-op. It sees a single group in the rule, so it invokes the evaluator and ignores the result. You’re ignoring ([some “b”]) the evaluation product…so no-op.

The double there should run the code and splice, so in this case that just makes [some “c”] the next rule.

Hence this should fail, the b’s won’t match.

I’ll do some work on the retriggering of groups. It’s a little weird because you have a step where if it’s a double group you have to process it beforehand to see if it’s a WORD!, but then if it’s a word you have to retrigger it to see if it’s a group (!). I guess that just means do group processing if necessary, do word lookup if necessary, then loop up to do group processing if necessary.

Will have to make sure there’s a cancellation opportunity if you somehow produce an infinite loop on this.

#5

I proposed another I-think-awesome feature in chat, which is that LOGIC! #[true] be a no-op in parse (same as BLANK!) while #[false] not match anything.

@rgchris has summarized:

``````parse "abc" ["a" ((true)) (-- "this prints") ((false)) to end (-- "this doesn't")]

foo?: true parse "abc" [foo? "a" (foo?: false) "b" foo? (-- "doesn't print")]
``````

This eliminates the need for PARSE’s IF construct. IF would take a condition as a parameter, in a GROUP!, and continue the parse if the condition were truthy and cease matching if it were falsey. But that was one conditional construct, with the oddity that it was “single-arity”. So it created questions about what its branch would be, where was the EITHER or SWITCH, etc.

Now instead of if (x = 10) you can just say ((x = 10)), and you have all the power this implies. If you want to stop a parse from continuing a match, you can do so just by having whatever you put in that double group evaluate to false! Should you feel that’s too obtuse, you might also write ((if x != 10 [false])) and it would work just as well, using null to proceed matching with an opt-out vs. by returning true.

And if you had if (condition) before and it was a logic, you can just say condition !

A major benefit in my eyes is that FALSE can take the place of FAIL–as FAIL has now come to be tightly associated with raising errors. I’d been worried about how to fix that, and this takes care of it.

Of course, as with INTEGER! and BLANK! and WORD! for keywords, you won’t be able to match literal logics any more with TRUE or FALSE. You’ll need quote ((condition)), quote #[true], etc. This is par for the course.

#6

Cleaner, more concise. I like!
I haven’t used the conditional parse keywords very much, so I’m looking into digging into it with this nicer approach.

#7

I was confused by it, because I thought that IF was arity 2 and was running the rule after it. But it doesn’t run any rule at all, it just directs continue-or-not.

Here’s the one example I have of me using it, in the implementation of FUNCTION going over the spec to look at the locals and args. I had to basically hack up EITHER by using if (var) […] | if (not var) […]

``````    if (var) [
set var: any-word! (
append exclusions var ;-- exclude args/refines
append new-spec var
)
|
set other: [block! | text!] (
append/only new-spec other ;-- spec notes or data type blocks
)
]
|
if (not var) [
set var: set-word! ( ;-- locals legal anywhere
append exclusions var
append new-spec var
var: _
)
]
``````

Complex code is going to be tricky no matter how you read it. But I think it’s more clear by just breaking into normal code, and getting to use an EITHER to pick your rules:

``````    ((either var [[
set var: any-word! (
append exclusions var ;-- exclude args/refines
append new-spec var
)
|
set other: [block! | text!] (
append/only new-spec other ;-- spec notes or data type blocks
)
]][[
set var: set-word! ( ;-- locals legal anywhere
append exclusions var
append new-spec var
var: _
)
]]))
``````

Now the sky’s the limit on your conditional constructs.

This further tightens the notion that PARSE never sees what you put in a single GROUP!..it always executes and gets discarded, while double groups always splice. That’s been a major sticking point for me in the past, and we’re killing off the things that used it… IF, RETURN… bonus!

#8

Thumbs up on the doubled groups here-- but in the improved code, why are those EITHER blocks also doubled up?

#9

Because the doubled group wants to return a BLOCK! that’s a parse rule. Without the inner block, it would try to run the PARSE code…in DO.

``````parse "aaa" [((if true [some "a"]))] // gives error, SOME is unset, etc.

parse "aaa" [((if true [[some "a"]]))] // works
``````

If you’re writing code like this and that bothers you, then stylistically you could space it out.

``````parse "aaa" [((if true [ [some "a"] ]))] // works
``````

Or put it in a variable, etc.

#10

Ah ah ah, yes, thanks. Makes perfect sense, and was in some other examples earlier.

#11

Once upon a time, I believed that doubling brackets up was ugly, and hence there needed to be an IF/ONLY that would not evaluate branches.

@MarkEye didn’t think /ONLY was better. Over time, I changed my mind to think /ONLY wasn’t worth it, and double brackets weren’t so bad. Good, even. You say what you mean, and that’s what it looks like.

Eventually the idioms can grow on you, and you find other ways to deal with it.

#12

Do you think this would be clearer?

``````((either var '[
set var: any-word! (
append exclusions var ;-- exclude args/refines
append new-spec var
)
|
set other: [block! | text!] (
append/only new-spec other ;-- spec notes or data type blocks
)
] '[
set var: set-word! ( ;-- locals legal anywhere
append exclusions var
append new-spec var
var: _
)
]))
``````

I’m seriously contemplating the value of a separate datatype like LIT-BLOCK! (or just generically the idea that all types have a LIT-form, the so-called “lit bit” proposal).

#13

I like the LIT-BLOCK! form too. It’s not a huge difference but it is cleaner here.

#14

Upon further reflection, I probably believe lit-THING! has to become THING! in the evaluator, and lit-lit-THING! has to become lit-THING!.. in a fairly regimented way.

Previously I thought that if something had no evaluative behavior that a lit of it would also have no evaluative behavior. But this compromises the overall usefulness of the feature in the system and API when you’re trying to escape something generically and don’t know what it is.

Upshot is that if LIT-BLOCK! becomes BLOCK!, there would be no difference between `if x [y]` and `if x '[y]` because the branch is an evaluative slot. You’d have to write `if x ''[y]` so that IF would actually receive a LIT-BLOCK! in its branch.

At which point, I’m not convinced the weirdness (and having to explain it) outweighs the regularity of if x [ [y] ], which may be bulky but is trivial to understand.

#15

Agreed. Doesn’t seem like the juice is worth the squeeze, so to speak.

#16

One thing I notice about the evaluative parity of literal blocks and regular blocks is that it can act as a comment, of sorts:

`````` either condition [ // code fragment producing parse rule
blah blah blah
\[some "a"]
][
blah blah blah
\[thru <terminal>]
]
``````

It might be clearer that you are evaluating to that block as a value, and it’s not a parameter to `blah blah blah`.

So it may have some kind of idiomatic usage.