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.
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 this with BLOCK! isotopes. For instance, SPREAD produces them:
ren-c>> compose [value: (block), (spread block)] == [value: [a b c], a b c]
You can mix and match splicing parts and non-splicing parts in the same COMPOSE!
It gets rid of an instance of /ONLY, which has always been something that causes head-scratching to new users ("only what?")
This is now in sync with APPEND's default of not 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. I really believe this is the better behavior for people who are writing and reading the code.
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 VOID. So when un-taken IF provides another non-reified state of VOID vs. NULL, it provided 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. )
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 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
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,
We now have the ability to run functions on the groups before you splice them.
So if you want to define COMPOSE that acts historically like Rebol2 (splicing unless you say /ONLY), here's one way you can do it:
compose2: adapt augment :compose [/only] [ if not only [ predicate: func [group <local> product] [ either any-array? product: eval group [spread product] [:product] ] ] ]
We added the /ONLY refinement, and if you don't use it then it adds a processing function for arrays. This gives you the historical result!
This only scratches the surface of what's possible, with these bendable and useful ergonomics.