I’m doing some work involving injecting code into held expressions, and found that I was repeating myself in a couple areas related to the available `Replace`

and `ReplacePart`

syntaxes. Conveniently, `Extract`

offers us an optional third parameter where we can wrap the extracted contents in an arbitrary head before evaluation. From the documentation:

```
Extract(expr,pos,h)
extracts parts of expr, wrapping each of them with head h before evaluation.
```

Unfortunately, `Replace`

does not offer the same convenience argument, so I created a `ReplaceThen`

function which adds this option.

Additionally, I could not find any function which performs a `Replace`

-like operation (including arbitrary pattern rules, not just substitution of one part with a verbatim-substituted replacement part), but only on a portion of an expression specified by an arbitrary part specification (not just a level specification). So… I created a `ReplaceAt`

function which adds this functionality.

That said, I’m a bit of a newbie, and I want to make sure that:

- I considered all the edge-cases, and my code is “functionally correct” in the general case
- Performance is reasonably-close to optimal, for code written in native WFL. For example, I considered an approach based upon using
`Cases`

and `Position`

, but decided that the below implementation would be more efficient.
- Pattern guards are effective without being overly-onerous
- Flexibility is sufficient enough for functions intended to cover general-case use — do you have any use-cases you run into yourself that aren’t solved by my functions, but could be with reasonable modifications? Do you have any use-cases that might motivate me to create a 3rd/N’th function within this family of
`Replace`

extensions?
- Forget about the “…, for code written in native WFL” restriction above. Would you use these functions for general-purpose use involving large replacement operations as-written, or might these warrant being written in C? My WFL programming skills are actually significantly weaker than their C equivalents, so it makes little difference to me. My guess is that since
`Replace`

, `ReplacePart`

, `Extract`

, and `Dispatch`

are all heavily-optimized already, it’s probably not worth writing everything from scratch.

This post is a request-for-comment (RFC) on these two functions in-general, with a specific emphasis on the above questions. The code is below.

First, some boilerplate: setup a `HoldComplete`

-like wrapper, and some dispatch tables for modifying user-provided rules and level specifications:

```
ClearAll(ReplaceAt`HoldComplete, partSpecPatt, replaceRulePatt,
levelSpecPattern, ReplaceThen, ReplaceAt)
(* Surrogate for HoldComplete: in case rules themselves transform HoldComplete *)
SetAttributes(ReplaceAt`HoldComplete, HoldAllComplete)
(* Boilerplate pattern guards *)
partSpecPatt = _Integer | _Span | All | {partSpecElements___ /; (And @@
Thread@Unevaluated@MatchQ({partSpecElements}, partSpecPatt))};
replaceRulePatt = _Rule | _RuleDelayed | {(_Rule | _RuleDelayed) ..};
levelSpecPattern = _Integer | All | Infinity |
-Infinity | {Repeated(_Integer | Infinity | -Infinity, {1, 2})};
(* Translate arbitrary rule(s) to never modify ReplaceAt`HoldComplete *)
(* e.g. if the rule was _(x_):>Identity(x) *)
ruleExceptTransform =
Dispatch((h : Rule | RuleDelayed)(l_, r_) :>
h(Except(ReplaceAt`HoldComplete | _ReplaceAt`HoldComplete, l), r));
(* Translate level spec to account for ReplaceAt`HoldComplete *)
levelSpecTransform =
Dispatch({{n1_, n2_Integer?Positive} :> {n1, n2 + 1},
n_Integer?Positive :> n + 1, {n_Integer?Positive} :> {n + 1}});
(* Sugar: make user calls to Options(...) return something *)
Options(ReplaceThen) = Options(Replace);
Options(ReplaceAt) = Options(Replace);
```

Next, here are `ReplaceThen`

and `ReplaceAt`

:

```
With({ruleExceptTransform = ruleExceptTransform,
levelSpecTransform = levelSpecTransform},
ReplaceThen(expr_, rules : replaceRulePatt,
levelSpec : levelSpecPattern : {0}, wrap_Symbol : Identity,
opts : OptionsPattern()) := wrap @@ Replace(
ReplaceAt`HoldComplete(expr),
Replace(rules, ruleExceptTransform, {0, 1}),
Replace(levelSpec, levelSpecTransform),
opts
);
ReplaceAt(expr_, rules : replaceRulePatt, partSpec : partSpecPatt,
levelSpec : levelSpecPattern : {0}, opts : OptionsPattern()) :=
ReplacePart(
Unevaluated@expr,
Replace(
Replace(
Extract(Unevaluated@expr, partSpec, ReplaceAt`HoldComplete),
Replace(rules, ruleExceptTransform, {0, 1}),
Replace(levelSpec, levelSpecTransform),
opts
),
ReplaceAt`HoldComplete(x_) :> RuleDelayed(partSpec, x)
)
);
)
```

We might as well support Operator forms of the above, while we’re at it:

```
ReplaceThen(rules : replaceRulePatt,
levelSpec_ : levelSpecPattern : {0}, wrap_Symbol : Identity,
opts : OptionsPattern()) :=
Function(expr, ReplaceThen(expr, rules, levelSpec, wrap, opts),
HoldFirst) /. DownValues@ReplaceThen;
(* Remember Unevaluated if not inlining with DownValues *)
ReplaceAt(rules : replaceRulePatt, partSpec : partSpecPatt,
levelSpec : levelSpecPattern : {0}, opts : OptionsPattern()) :=
Function(expr,
ReplaceAt(expr, rules, partSpec, levelSpec, opts)) /.
DownValues@ReplaceAt;
```

Alternatively, I could have written `ReplaceAt`

in terms of `ReplaceThen`

, but I think that my formal proposal for these functions would be as-written above. The below alternative relies upon the fact that `RuleDelayed`

consumes `Unevaluated`

from any expression which starts with `Unevaluated`

:

```
ReplaceAt(expr_, rules : replaceRulePatt, partSpec : partSpecPatt,
levelSpec : levelSpecPattern : {0}, opts : OptionsPattern()) :=
With({subExpr =
Extract(Unevaluated@expr, partSpec,
ReplaceThen(rules, levelSpec, Unevaluated, opts))},
ReplacePart(expr, partSpec :> subExpr))
ReplaceAt(rules : replaceRulePatt, partSpec : partSpecPatt,
levelSpec : levelSpecPattern : {0}, opts : OptionsPattern()
) := With({replaceThenOp =
ReplaceThen(rules, levelSpec, Unevaluated, opts)},
Function(expr,
With({subExpr = Extract(Unevaluated@expr, partSpec, replaceThenOp)},
ReplacePart(expr, partSpec :> subExpr))))
```

That concludes the function definitions. Now, let’s define a test expression to run through the two functions and their Operator-form versions:

```
ClearAll(F, G)
testExpr = Hold(
1 + 1, F(Plus(a, b), Plus(c, d), Times(e, Plus(f, g))), 2 + 2, h + i
);
```

Then, let’s test `ReplaceThen`

:

```
ReplaceThen(testExpr, F :> G, {2}, Heads -> True)
ReplaceThen(Unevaluated(3 + 3), Plus :> Times, {1}, Hold, Heads -> True)
ReplaceThen(Plus :> Times, {1}, Hold, Heads -> True)
%@Unevaluated(3 + 3)
```

The output is what I would expect:

```
Hold(1 + 1, G(a + b, c + d, e * (f + g)), 2 + 2, h + i)
Hold(3 * 3)
Function(expr$,
Hold @@ Replace(ReplaceAt`HoldComplete(expr$),
Replace(Plus :> Times, Dispatch(Length : 1), {0, 1}),
Replace({1}, Dispatch(Length : 3)), Heads -> True), HoldFirst)
Hold(3 * 3)
```

Now, let’s test `ReplaceAt`

:

```
ReplaceAt(testExpr, Plus :> Times, {2}, {0, Infinity}, Heads -> True)
ReplaceAt(testExpr, F :> G, {2}, {1}, Heads -> True)
ReplaceAt(testExpr, x_ :> x^2, {2}, {-1})
ReplaceAt(testExpr, x_ :> x^2, {2}, {-Infinity, Infinity})
ReplaceAt(x_ :> x^2, {2}, {-Infinity, Infinity})
%@testExpr
```

Again, the results match what I would expect:

```
Hold(1+1,F(a * b,c * d,e * (f * g)),2+2,h+i)
Hold(1+1,G(a+b,c+d,e * (f+g)),2+2,h+i)
Hold(1+1,F(a^2+b^2,c^2+d^2,e^2 * (f^2+g^2)),2+2,h+i)
Hold(1+1,F((a^2+b^2)^2,(c^2+d^2)^2,(e^2 * (f^2+g^2)^2)^2)^2,2+2,h+i)
Function(expr$,
ReplacePart(Unevaluated(expr$),
Replace(Replace(
Extract(Unevaluated(expr$), {2}, ReplaceAt`HoldComplete),
Replace(x_ :> x^2, Dispatch(Length : 1), {0, 1}),
Replace({-(Infinity), (Infinity)}, Dispatch(Length : 3))),
ReplaceAt`HoldComplete(x$_) :> {2} :> x$)))
Hold(1+1,F((a^2+b^2)^2,(c^2+d^2)^2,(e^2 * (f^2+g^2)^2)^2)^2,2+2,h+i)
```

That said, perhaps there are some edge-cases that I haven’t thought through, or performance/flexibility could be increased. Or perhaps you can think of an alternative way to perform the same tasks, written in WFL, which is simply more elegant.

Thanks in advance for feedback / critiques!

EDIT: I’ll start by critiquing myself — I noticed that I’m inconsistent in the Operator forms of `ReplaceThen`

and `ReplaceAt`

… `ReplaceThen`

uses `HoldFirst`

, while `ReplaceAt`

does not. On the one hand, I want to be able to use the Operator forms of these functions as the 3rd argument to `Extract`

, receive an unevaluated expression, and pass-on that expression, still `Unevaluated`

, to downstream `Replace`

/`ReplacePart`

functions. On the other hand, if the user provides a variable to the Operator forms of my functions (e.g. `myReplaceAtOperator(testExpr)`

), I want to support that too. But if I make the Operator forms include `Function(..., HoldFirst)`

*and* pass-on `Unevaluated(expr)`

to downstream functions, then `Replace`

/`ReplacePart`

will just receive a `Symbol`

, not the expression it contains. Still thinking this part through… I may just make an `OptionValue`

so that you can choose when you want the Operator to have `HoldFirst`

and when you do not…