1
votes

I am a noob at metaprogramming so maybe I am not understanding this. I thought the purpose of the @nloops macro in Base.Cartesian was to make it possible to code an arbitrary number of nested for loops, in circumstances where the dimension is unknown a priori. In the documentation for the module, the following example is given:

@nloops 3 i A begin
    s += @nref 3 A i
end

which evaluates to

for i_3 = 1:size(A,3)
    for i_2 = 1:size(A,2)
        for i_1 = 1:size(A,1)
            s += A[i_1,i_2,i_3]
        end
    end
end

Here, the number 3 is known a priori. For my purposes, however, and for the purposes that I thought nloops was created, the number of nested levels is not known ahead of time. So I would not be able to hard code the integer 3. Even in the documentation, it is stated:

The (basic) syntax of @nloops is as follows:

  • The first argument must be an integer (not a variable) specifying the number of loops.

...

If I assign an integer value - say the dimension of an array that is passed to a function - to some variable, the nloops macro no longer works:

b = 3
@nloops b i A begin
    s += @nref b A i
end

This returns an error:

ERROR: LoadError: MethodError: no method matching _nloops(::Symbol, ::Symbol, ::Symbol, ::Expr)
Closest candidates are:
  _nloops(::Int64, ::Symbol, ::Symbol, ::Expr...) at cartesian.jl:43
...

I don't know how to have nloops evaluate the b variable as an integer rather than a symbol. I have looked at the documentation and tried various iterations of eval and other functions and macros but it is either interpreted as a symbol or an Expr. What is the correct, julian way to write this?

1

1 Answers

3
votes

See supplying the number of expressions:

julia> A = rand(4, 4, 3)  # 3D array (Array{Int, 3})

A generated function is kinda like a macro, in that the resulting expression is not returned, but compiled and executed on invocation/call, it also sees the type (and their type parameters of course) of the arguments, ie:

  • inside the generated function, A is Array{T, N}, not the value of the array.
  • so T is Int and N is 3!

Here inside the quoted expression, N is interpolated into the expression, with the syntax $N, which evaluates to 3:

julia> @generated function mysum(A::Array{T,N}) where {T,N}
           quote
               s = zero(T)
               @nloops $N i A begin
                   s += @nref $N A i
               end
               s
           end
       end
mysum (generic function with 1 method)

julia> mysum(A)
23.2791638775186

You could construct the expression and then evaluate it, ie.:

julia> s = 0; n = 3;

julia> _3loops = quote
           @nloops $n i A begin
               global s += @nref $n A i
           end
       end
quote
    @nloops 3 i A begin
        global s += @nref(3, A, i)
    end
end

julia> eval(_3loops)

julia> s
23.2791638775186

I have scrubbed manually the LineNumberNodes from the AST for readability (there is also MacroTools.prettify, that does it for you).

Running this example in the REPL needs to declare s as global inside the loop in Julia 1.0.