Substituting Expression for Symbol in Nested Expression

Let’s suppose I have a situation which necessitates substituting an expression for a symbol in a nested expression. Let’s further suppose that I am unable to acomplish this by acting on the string before parsing using string interpolation

To illustrate the end goal, it is desired to substitute the symbol y for 10*a
such that expr_old becomes that shown in expr_new as follows:

expr_old = :(3x + (5x +7y^3)^2 + 4z)
becomes
expr_new = :(3x + (5x +7*(10a)^3)^2 + 4z)

Maybe something like this?

# General case: do nothing (identity)
substitute(x, _) = x

# Symbol: substitute if necessary; otherwise, do nothing
function substitute(s::Symbol, pair)
    s == pair.first && return pair.second
    s
end

# Expression: recursively perform the substitution in all
# components of the expression
function substitute(e::Expr, pair)
    Expr(substitute(e.head, pair), substitute.(e.args, Ref(pair))...)
end
julia> expr_old = :(3x + (5x +7y^3)^2 + 4z)
:(3x + (5x + 7 * y ^ 3) ^ 2 + 4z)

julia> expr_new = substitute(expr_old, :y => :(10*a))
:(3x + (5x + 7 * (10a) ^ 3) ^ 2 + 4z)



EDIT: This can be simplified using MacroTools:

using MacroTools

function substitute(e::Expr, pair)
    MacroTools.postwalk(e) do s
        s == pair.first && return pair.second
        s
    end
end
5 Likes

Brilliant. I tried recursive methods akin to your first approach, but was unable to get something that worked.

I appreciate both of these solutions being provided. I have learned something new studying each.

Thanks for this great thread. I dug it up, and realized really how versatile it is! I wanted to follow on to this with perhaps a more complicated example that is stumping me.

using MacroTools
function substitute(e::Expr, pair)
    MacroTools.postwalk(e) do s
        s == pair.first && return pair.second
        s
    end
end
ex = :(sum(x[i] for i=1:4) - y[1] * y[2] + z)
substitute(ex, :(x[2]) => 3) # doesn't work
substitute(ex, :(y[2]) => 1) # works
substitute(ex, :(z) => 1) # works
substitute(ex, :x => [1,2,3,4]) # works

Since the array substitution works, am I doing something syntactically wrong with the first :(x[2]) substitution? I would appreciate the help!

What you need to keep in mind, is that expression matching doesn’t know anything about the values of identifiers, so since the literal expression :(x[2]) never actually occurs in the expression ex, only :(x[i]), your first example can’t work. It is possible to achieve something like this using IRTools.jl and recursively stepping through the lowered code of functions instead of just walking an expression, but that’s unrelated to this thread. Feel free to open a new one though, if you have some specific problem in mind that you want to solve or have any further questions.

1 Like

Of course, now I feel pretty silly. I guess the workaround is to substitute for the entire array instead and/or to collect the generators in the expressions. Thanks @simeonschaub, and I will definitely create a new thread in the future.

1 Like