1
votes

OK my title isn't great but it's easily explainable with an example.

julia>a = :(1 + 2)
julia>b = :(2 + 1)

julia>a == b
false

I have two expressions a and b. I would like to know if they will give me the same results without being evaluated.

I though that commutative operators like + or * could infer that the results would be the same.

EDIT: Another way to understand it is to compare a very specific subset of expressions that can infer the commutativity of the function: Expr(:call, +, a, b) <=> Expr(:call, +, b, a)

2

2 Answers

2
votes

We can write a fairly simple function to check if two arrays have the same elements, modulo ordering:

function eq_modulo_ordering!(xs, ys)  # note !, mutates xs and ys
    while !isempty(xs)
        i = findfirst(isequal(pop!(xs)), ys)
        i === nothing && return false
        deleteat!(ys, i)
    end
    isempty(ys)
end
eq_modulo_ordering(xs, ys) = eq_modulo_ordering!(copy(xs), copy(ys))

We can use then use this function to check if two top-level expressions are equivalent.

function expr_equiv(a::Expr, b::Expr, comm)
    a.head === b.head || return false
    a.head === :call || return a == b
    a.args[1] ∈ comm || return a == b

    eq_modulo_ordering(a.args, b.args)

end
expr_equiv(a, b, comm) = a == b
expr_equiv(a, b) = expr_equiv(a, b, [:+])

In the case that we want to check that two expressions are fully equivalent beyond the top-level, we could modify our functions to use mutual recursion to check if the subexpressions are expr_equiv, rather than isequal.

function eq_modulo_ordering!(xs, ys, comm)  # note !, mutates xs and ys
    while !isempty(xs)
        x = pop!(xs)
        i = findfirst(b -> expr_equiv(x, b, comm), ys)
        i === nothing && return false
        deleteat!(ys, i)
    end
    isempty(ys)
end
eq_modulo_ordering(xs, ys, comm) = eq_modulo_ordering!(copy(xs), copy(ys), comm)

function expr_equiv(a::Expr, b::Expr, comm)
    a.head === b.head || return false
    a.head === :call || return a == b
    a.args[1] ∈ comm || return all(expr_equiv.(a.args, b.args, Ref(comm)))

    eq_modulo_ordering(a.args, b.args, comm)
end
expr_equiv(a, b, comm) = a == b
expr_equiv(a, b) = expr_equiv(a, b, [:+])

We can now use expr_equiv as expected, optionally supplying a list of functions which are commutative.

julia> expr_equiv(:((a + b + b) * c), :((b + a + b) * c))
true

julia> expr_equiv(:((a + a + b) * c), :((b + a + b) * c))
false

julia> expr_equiv(:(c * (a + b + b)), :((b + a + b) * c), [:+, :*])
true
2
votes

This is impossible. Figuring out whether two programs have the same result without evaluating them is called the Function Problem and is provably equivalent to solving the Halting Problem.

It is not possible to compute whether to pieces of code will have the same result.