Historical REPLACE in Redbol allows you to pass in a function, which is called on each match position...and can return the thing to replace with:
r3-alpha>> replace/all "abcba" "b" func [x] [probe x random 10]
"bcba"
"ba"
== "a1c5a"
(Ren-C actually passes you the head and the tail of the match, if your function is arity-2)
Now imagine the replacement function returning a NONE. Or a VOID. Or a NULL. Or a VETO.
-
NONE should clearly be positive nothingness, and do the replacement...erasing the pattern.
- In the modern conception, VOID does the same thing as NONE in cases like this. Having two ways to say nothing is useful here, because the unstable antiform property won't be right for all situations... but right for some.
-
NULL I think should be an error, indicating some bug or accidental oversight occurred in your handler; this is the general expectation of passing NULL where a value is expected
-
VETO should opt out of the replacement... but probably not all the replacements.
-
I wonder if you use REPLACE:ONE, and the handler returns VETO, if that means it didn't count as a "replacement" and it should keep looking before it thinks it's "found one"?
-
This is a difference in that a function that returns VETO on each call does not give you the same experience as if you passed in VETO as the original replacement, which would have caused the function as a whole to return NULL. (I don't think that is bad--it seems the more useful choice than having any call produce VETO end the replace--I'm just making an observation).
-
Impact On The Code
What was there for using the function's result (VALUE) was this:
pos: change:part pos (opt value) tail
if one [break] ; stop the loop and return
That means that was was supported before was that the handler could return NULL and it would count as nothing, and CHANGE would erase the information. Unstable antiforms were not supported (returning a void from the handler would have been unable to assign (value: ...))
Let's throw out the idea of OPT-ing NULLs, and instead support unstable antiform returns from the handler. So they're assigned via (^value: ...) instead of (^value: ...) when coming back from the function, and should be fetched with ^value
pos: change:part pos ^value tail
if one [break] ; stop the loop and return
This doesn't give a graceful error on NULL, it just PANICs (in the guts of REPLACE, as far as the user experiences it). But it may be that some machinery installs an error handler and you don't have to test for null explicitly. Let's put "pretty" error handling aside and just accept an ugly error if an error was what we wanted--for now.
I said "VETO should opt out of the replacement...but probably not all the replacements". However a VETO passed in VALUE would now make CHANGE return NULL... which would mean POS: gets NULL... which would crash the loop.
There are tools to help avoid the NULL overwrite, such as MAYBE... which would leave POS as is when VALUE was VOID:
pos: maybe change:part pos ^value tail ; ???
if one [break] ; stop the loop and return
But this doesn't give you what you want, because you want to skip to TAIL, vs. try running the replacement again at the same position after getting a VOID. And it would run the BREAK on the ONE case, which I'm suggesting you probably don't want.
I think there's only so much a pipeline of code can do. Sometimes you need a branch!
if veto? ^value [
pos: tail
continue
]
pos: change:part pos ^value tail
if one [break]
Or if you want to leave room for some code to run after the replacement-or-non-replacement:
if veto? ^value [
pos: tail
] else [
pos: change:part pos ^value tail
if one [break]
]
; you could have some code here before the loop continues
And if you want to factor out the assignment, and use EITHER:
pos: either veto? ^value [
tail
][
change:part pos ^value tail
elide if one [break] ; so CHANGE's result is what POS gets
]
; you could have some code here before the loop continues
![]()
This Is A Solid Story
It does make you think a little bit. But being able to choose to "opt in" or "opt out" of a REPLACE gives you the ability to make the overall REPLACE return NULL or not based on what you pass in as the pattern or replacement... and that's a powerful new ability.