COMPOSE is one of Rebol's most useful and recognizable constructs. As with most-things-Ren-C, it has evolved to become far more powerful.
No Splicing By Default, Use ((...)) To Request Splicing
One of the immediately noticeable differences is that there is no /ONLY option any longer. A plain GROUP! will not splice its evaluative result. So if block: [a b c], it is different from historical Redbol:
rebol2/r3-alpha/red>> compose [value: (block)] == [value: a b c] ren-c>> compose [value: (block)] == [value: [a b c]]
If you want to have something splice, you do it with the specific notation of a doubled-group:
ren-c>> compose [value: (block), ((block))] == [value: [a b c], a b c]
You can mix and match splicing parts and non-splicing parts in the same COMPOSE!
There's a strong visual signal that multiple-elements might be spliced in.
- It's intuitive as well, because
((...))is "fatter" than
- It's intuitive as well, because
It gets rid of an instance of /ONLY, which has always been something that causes head-scratching to new users ("only what?")
While this deviates from APPEND's default of splicing, I am convinced that practice has shown that not splicing is a safer and more intuitive default. You're so often dealing with splicing several values at a time that the odds of you knowing precisely what data type all of them are become lesser. As a higher-level tool, it should cater to the higher-level needs...and that's not splicing unless you ask. I really believe this is the better behavior for people who are writing and reading the code.
Both (...) and ((...)) will Vaporize Voids!
In my list of "non-negotiables", I've always said this had to be true, somehow:
>> compose [<a> (if false [<b>]) <c>] == [<a> <c>]
For quite some time, a conditional like IF that didn't run its branch would return NULL. Because NULL couldn't be put in a block, it seemed like it was a good fit for signaling vaporization of a clause in a COMPOSE. But I was nervous because as NULL came to mean "soft failure", this felt like you could be sweeping a failure under the rug.
Yet it was heavy-handed to need to say something like
(maybe if false [<b>]) to turn the NULL into a purely invisible VOID. So it was a relief when the mechanics of "impure invisibility" allowed conditionals like IF to safely be void instead of null when a branch did not run.
The result is the best of both worlds, where NULL can give a specific error tied to null splicing.
>> compose [<a> (select [a 10 b 20] 'c) <c>] ** Script Error: non-NULL value required (see MAYBE, TRY, REIFY) >> compose [<a> (maybe select [a 10 b 20] 'c) <c>] == [<a> <c>]
(Trust me, this turns out to be powerful--not a hassle. I'll show exactly how insaneo-style that is in an upcoming demonstration, so be looking forward to it. )
Decorated Groups (including quoted) Apply Their Decorations!
Ren-C has a lot of variations of GROUP!:
''''(quoted!)yes, any number of quotes!
COMPOSE does useful magic for you, and if the type a GROUP! evaluates to supports the decoration, it will be applied!
>> fruits: [apple banana] >> dispositions: [favorite least-favorite] >> compose [(dispositions.1): '@(second fruits)] == [favorite: '@banana]
Once you have this, you won't want to go back. The premise of the language is being able to dynamically play with code and generate structures on the fly. This makes that feel extremely seamless.
One little interesting thing to observe is that although you can't splice nulls, you can splice a quoted NULL:
>> compose [''(second [a])] == ['']
These little details turn out to be important: if you look closely at the implementation of something like the console you will see how they permit no-hassle handling of what may seem like difficult edge cases!
You Can Label the Specific Groups You Want Composed!
One of the good things about templating is to be able to write most of your code normally, and then point out just the parts you want to substitute. So if you're using so many groups that just being in a GROUP! isn't distinguishing what you want to substitute, tagged COMPOSE to the rescue:
You can pick whatever label you want, and the first item of each group will be checked against it:
>> compose/label [(1 + 2) (<*> 1 + 2) (1 + 2)] <*> == [(1 + 2) 3 (1 + 2)]
As a neat shorthand for this, a skippable TAG! parameter can be used:
>> compose <*> [(1 + 2) (<*> 1 + 2) (1 + 2)] == [(1 + 2) 3 (1 + 2)]
The TAG! has to be given literally between the COMPOSE and the expression you want to compose. (This is a requirement for
Doubled-groups still have /ONLY semantics:
>> x: [a b c] >> compose <$> [(1 + 2) (<$> reverse copy x) ((<$> reverse copy x)) ((1 + 2))] == [(1 + 2) c b a [c b a] ((1 + 2))]
You don't have to use symbols...any tag will do. Could be a whole word with meaningful names, which might be valuable if you were doing it in several steps...where earlier phases could leave tags for later phases to compose. You might also tag with numbers,
Predicate Functions Can Process the Compose Slots!
We now have the ability to run functions on the slots before you splice them. These functions are specified via the "predicate" convention (for the moment it's a "refinement-style" PATH!, but will be a TUPLE! when generalized tuples are implemented)
In order to allow your return result to ask for splicing or not, you return using the typical conventions for APPEND. e.g. a quoted value will not splice...
>> nonsplice-hook: function [x] [quote reduce [(x + 1) (x + 2)]] >> compose/predicate [(10 + 20) (30 + 40)] :nonsplice-hook == [[31 32] [71 72]]
But a non-quoted value will splice:
>> splice-hook: function [x] [reduce [(x + 1) (x + 2)]] >> compose/predicate [(10 + 20) (30 + 40)] :splice-hook == [31 32 71 72]
This leads up to revealing a clever design trick about what ((...)) GROUP!s are actually doing...
...they don't specifically mean "don't splice", they are asking to not apply the predicate...where the default predicate for plain (...) groups is simply META!
So what ((...)) does is unaffected by the predicate:
>> nonsplice-hook: function [x] [quote reduce [(x + 1) (x + 2)]] >> compose/predicate [(10 + 20) ((reverse [a b c]))] :nonsplice-hook == [[31 32] c b a]
So if you want to define COMPOSE that acts historically like Rebol2 (splicing unless you say /ONLY), here's how you can do it:
compose2: adapt augment :compose [/only] [ if not only [ predicate: :blockify ; in block if not already, only blocks splice ] ]
We added the /ONLY refinement, and if you don't use it then it will put anything that isn't a block already into a block. Since the result of the predicate is processed using "APPEND semantics", this give syou the historical result!
This only scratches the surface of what's possible, with these bendable and useful ergonomics.