I think a MWE would be really helpful here. Maybe Iβm just lacking imagination, but I canβt quite figure out why the code that generates indices needs to be differentiated in the first place.
Furthermore, the proposed macros (except for the one in the opening post) are equivalent to inlined functions.
For example
julia> f() = (@IJ 3)
f (generic function with 1 method)
julia> f()
([0, 0, 0, 0, 1, 1, 1, 2, 2, 3], [0, 1, 2, 3, 0, 1, 2, 0, 1, 0])
julia> @code_lowered f()
CodeInfo(
1 ββ Core.NewvarNode(:(@_2))
β %2 = Core.apply_type(Main.Vector, Main.Int)
β I = (%2)()
β %4 = Core.apply_type(Main.Vector, Main.Int)
β J = (%4)()
β %6 = 3 < 0
ββββ goto #3 if not %6
2 ββ Main.print("The input must be equal to or bigger than zero.")
ββββ goto #11
3 ββ %10 = 0:3
β @_2 = Base.iterate(%10)
β %12 = @_2 === nothing
β %13 = Base.not_int(%12)
ββββ goto #11 if not %13
4 ββ %15 = @_2
β i = Core.getfield(%15, 1)
β %17 = Core.getfield(%15, 2)
β %18 = 0:3
β @_5 = Base.iterate(%18)
β %20 = @_5 === nothing
β %21 = Base.not_int(%20)
ββββ goto #9 if not %21
5 ββ %23 = @_5
β j = Core.getfield(%23, 1)
β %25 = Core.getfield(%23, 2)
β %26 = i + j
β %27 = %26 <= 3
ββββ goto #7 if not %27
6 ββ Main.push!(I, i)
ββββ Main.push!(J, j)
7 ββ @_5 = Base.iterate(%18, %25)
β %32 = @_5 === nothing
β %33 = Base.not_int(%32)
ββββ goto #9 if not %33
8 ββ goto #5
9 ββ @_2 = Base.iterate(%10, %17)
β %37 = @_2 === nothing
β %38 = Base.not_int(%37)
ββββ goto #11 if not %38
10 β goto #4
11 β %41 = Core.tuple(I, J)
ββββ return %41
)
The logic to construct I,J
is still present. All the macro does is wrap the whole machinery in an expression and return it. You could have equivalently copy-pasted that code into test
.
If n
is dynamic, i.e. its value only known at run-time, there is no way to make this work with a macro, which is expanded and evaluated at parse time.
However, a generated function can work. To quote from the manual
While macros work with expressions at parse time and cannot access the types of their inputs, a generated function gets expanded at a time when the types of the arguments are known, but the function is not yet compiled.
function to generate I,J
function IJ(n) # n is degree
I = Vector{Int}() # <- `I` will be obfuscated in the code made by the macro
J = Vector{Int}() # <- Same for `J`
if n < 0
print("The input must be equal to or bigger than zero.")
else
for i in 0:n
for j in 0:n
if i+j <= n
push!(I, i)
push!(J, j)
end
end
end
end
return (I, J)
end
@generated function fgen(::Val{n}) where n
ij = IJ(n)
return :( $ij )
end
julia> fgen(Val(3))
([0, 0, 0, 0, 1, 1, 1, 2, 2, 3], [0, 1, 2, 3, 0, 1, 2, 0, 1, 0])
julia> @code_lowered fgen(Val(3))
CodeInfo(
@ REPL[45]:1 within `fgen`
β @ REPL[45] within `macro expansion`
1 ββ %1 = Core.tuple([0, 0, 0, 0, 1, 1, 1, 2, 2, 3], [0, 1, 2, 3, 0, 1, 2, 0, 1, 0])
ββββ return %1
β
)
The downside is that n
needs to be wrapped in a value type, and excess code generation and compilation may occur when the function is called for many different values of n
.
This feels like hacking around the actual issue though.