Have you ever been parsing and wanted to call a plain old function on a value?
Let's say you are collecting some numbers, and you want to negate them.
>> parse [1 2 3] [collect some [
num: integer! (num: negate num) keep (num)
]]
== [-1 -2 -3]
You're stuck having to name it, transform it, and then (possibly) reference the name again.
UPARSE's mechanics actually make this a bit better already, since GROUP! results can be used directly:
>> parse [1 2 3] [collect some [
num: integer! keep (negate num)
]]
== [-1 -2 -3]
But you still have to cross over into a GROUP! if you want to do any negation, and use a name to move the parsed value into the domain of DO.
Meet A New Trick: ACTION! Combinators
You can pick whatever syntax you like for it (UPARSE is customizable, remember?)
But I'm using paths that start with slash to mean "call a normal function but acquire its arguments via the synthesized result of PARSE rules". (Consistent with the new role of leading-slash in EVAL.)
>> parse [1 2 3] [collect some [
keep /negate integer!
]]
== [-1 -2 -3]
You can call any function this way normally by providing each argument in a group.
>> x: 510
>> parse [] [/multiply (1 + 1) (x)]
== 1020
But that's easier read as ((1 + 1) * x)
. You of course don't want to do it unless at least some of your arguments come from a rule that's not a GROUP!:
>> data: copy ""
>> parse ["a" "b"] [some [/append:dup (data) text! (2)]]
>> data
== "aabb"
I think the primary usefulness is for functions without side-effects where you just want to do a quick transformation on some information you are assigning or collecting.
But that doesn't mean you can't write some interesting machines.
>> data: copy ""
>> parse ["abc" <reverse> "DEF" "ghi"] [
while [
/append (data) [
'<reverse> /reverse /copy text!
| text!
]
]
]
>> data
== "abcFEDghi"
(Note: with PATH!-based function composition, you should be able to write that as /reverse/copy
without the space, and it would be faster...the composition and one ACTION! combinator would be quicker than two ACTION! combinators.)
The foundations of Ren-C are strong, which is why such things can be made. All that stuff about MAKE FRAME! and ADAPT and AUGMENT etc. are about giving control of time and space to the users to build such combinators.
Does This Mean Fewer Combinators Needed?
Anything that doesn't advance the series position doesn't need to speak the combinator interface.
So, for instance, ELIDE.
>> parse ["a" "b" <c>] [collect [some keep text!] /elide tag!]
== ["a" "b"]
But ELIDE is rather useful, so aliasing it as a combinator to invoke it without the slash seems nice. However, this aliasing process should be cheap and easy... to "combinatorize" a function.
But as I say, a plain function doesn't know anything about advancing input and speaking the combinator protocol. What's great is how close I've made combinator protocol to ordinary functions. I'm kind of gloating a lot about it, because it's neat.
Can We Generalize a Rollback Mechanism?
COLLECT is set up so that if you do some KEEPs in a rule that ultimately fails, the keeps roll back.
One of the things limiting the usefulness of this mechanism for functions with side effects (APPEND as opposed to NEGATE) is that they don't get rolled back on failure.
I've wondered if there could be some kind of DEFER operation which captures GROUP! operations and only runs them if a certain point is reached. Being able to defer any rule that can have side effects might be nice, so that would include these ACTION! combinators. Worth thinking about.