What's wrong with this macro?

In theory, I have a macro that allows me to interpret

@← a = f(b...)

as either

f!(a, b...)

or

a = f(b...)

depending on whether f! is defined or not. My attempt at writing such a macro is

macro ←(input)
    # expression needs to be of form `a = f(b...)`
    a = input.args[1]
    f = input.args[2].args[1]
    f! = Symbol(f, "!")
    b = input.args[2].args[2:end]
    output = quote
        if (@isdefined $f!) && isa($f!, Function)
            return $f!($a, $(b...))
        elseif (@isdefined $f) && isa($f, Function)
            return $a = $f($(b...))
        else
            error("ERROR!")
        end
    end
    esc(output)
end

However, it does not seem to work as it should whenever I’m using it in local scope (e.g. some kind of function, etc.). Any idea why?

I think, you probably meant to write return $f!($a, $(b...)) instead of return $f!($a, $b...). The latter interpolates Any[:(b...)] as a literal into the function call and then that gets splatted at runtime into $f!, so you are effectively calling f!(a, :(b...)). If you add the parentheses, the array is actually splatted into the expression tree.

1 Like

BTW, do you know about BangBang.jl? It sounds like you are trying to achieve something very similar.

3 Likes

You are completely right. However, the problem persists.

I did not! Their macro @! is indeed doing something very similar. I’ll check that, thanks.

2 Likes

Could you share a full example of how you are calling it? That usually makes it a lot easier to help you.

Is the problem that isdefined doesn’t handle local scope? https://docs.julialang.org/en/v1/base/base/#Core.isdefined

I think I’m doing something like

f(b) = [1, 1, 1]
function f!(a, b)
    a[1] = 2
    a[2] = 2
    a[3] = 2
    a
end
function g(v)
    @← v[1] = f("!@£%")
    v
end

v = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
g(v)

which, however, seems to work just fine.

In actual practice, when running the following snippet

using RungeKutta
u0 = [2.0; 3.0; -14.0]
tspan = (0.0, 1.0)
problem = Lorenz(u0, tspan)
solver = Euler(h = 1e-3)
solution = solve(problem, solver)
plot(solution)

which calls my package RungeKutta.jl, I’m having trouble when changing from

f!(k[i], v, t[n] + h * c[i])

to

@← k[i] = f(v, t[n] + h * c[i])

lines 19-20 in step.jl.

It could very well be! Any alternative to @isdefined then?

EDIT: I’ve changed @isdefined with a custom one from https://discourse.julialang.org/t/check-whether-a-variable-is-defined/1018/3?u=jlapeyre which should work in local scope too, but, once again, without success.

EDIT 2: It turns out that, in this case, Julia correctly uses Base.@isdefined instead of Core.@isdefined, which is able to deal with the current scope.

1 Like

Getting rid of the returns solved the issue.

2 Likes