The Even More Powerful COMPOSE


#1

COMPOSE got a pretty cool super power with labeling, so that a "skippable" TAG! or FILE! could be used to point out just the slots you wanted the compose to apply to:

>> compose [(1 + 2) (1 + 2)]
== [3 3]

>> compose <*> [(1 + 2) (<*> 1 + 2)]
== [(1 + 2) 3]

>> compose % [(1 + 2) (% 1 + 2)]  ; even empty file is legal, which is succinct
== [(1 + 2) 3]

Then there's the new ability to use quoted groups, SET-GROUP!s, and GET-GROUP!s:

>> stuff: [word some/path [will be a triple quoted get-block!]]

>> compose ['(first stuff) (second stuff): ''':(third stuff)]
== ['word some/path: ''':[will be a triple quoted get-block!]]

Whoa. But wait, there's more! :slight_smile:

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)

>> addone: function [x] [x + 1]

>> compose /addone [(10 + 20) (30 + 40)]
== [31 71]

Putting things in doubled-up groups asks to not apply the post-processing function on that particular slot.
It still composes it, but just doesn't run the function:

>> compose /addone [(10 + 20) ((30 + 40))]
== [31 70]

Something very interesting is that if you use a function that puts its argument into a block (e.g. the ENBLOCK function), then that would be the same as doing historical Rebol's COMPOSE/ONLY...because the block would be spliced and leave its single element behind, even if that block was itself a block. Then, the (( )) would be a way of opting out and saying you wanted to splice blocks normally.

>> compose /enblock [([a b c]) (([d e f]))]
== [[a b c] d e f]

So that first [a b c] was transformed to [[a b c]], and then spliced in to lose the outer block. The doubled group was a request not to run enblock, so that block just got spliced.

Now here's the fun bit: enblock is the default predicate. This means that if you don't override it, you can mentally model the (( )) as an instruction to splice if it's a block!

>> compose [([a b c]) (([d e f]))]
== [[a b c] d e f]

This is a point of incompatibility with historical Rebol, because COMPOSE is effectively what was COMPOSE/ONLY unless you splice with (( )). But you can still write code that will work in either Ren-C or historical Rebol by putting things in doubled groups when you want them to splice...the (( )) will just be ignored in the older Rebol and it will consider all slots spliceable.

If you want the old behavior, just ask to use the identity function. Then it makes no difference whether the function is applied or not:

>> compose /identity [([a b c]) (([d e f]))]
== [a b c d e f]

This only scratches the surface of what's possible! I'm very interested to see how people use the features. (Don't get too attached to the PATH! notation, though...that's temporary, like I said.)


Pure and Refined: Simplifying Refinements to One or Zero Args
#2

Scrumptious enhancements here! :tongue:
I've used COMPOSE quite a bit in my code previously and was grateful for it, but it was just a normal rebol way of getting things done. The labeling and predicate changes make it much more likely that I'll be building a lot more of my code with this powerful function.


#3

Another instance of acting upon the 2018 motto... "Elevating the Art"!

(We'll see if 2019 needs another motto, besides "Please God, help us ship Beta/One this year." :p)