@nloops and @generated function

I know macros should only be used in very specific cases and I’m not an expert at all on the topic. Nevertheless I managed to make work a skeleton of my planned code (where actually the loop limits at each level are different and calculated). A simplified version is below.
The obvious thing I want to do now is of course to replace the value 4 using the parameter twon (passed to the function). I know this should be feasible using @generated function, block and $twon, but I do not succeed to find the right syntax.

using Base.Cartesian
using BenchmarkTools


function looper(lower,upper)
   twon=4
   val=zeros(Int32,twon)
   @nloops(4, v, i -> (i < twon) ? range(twon-i,v_{i+1}-1) : (lower:upper),
        begin 
            @nexprs 4 j -> val[j] = v_j
            @show val
            # Actual work on val from here on
        end
           )
end

looper(5,10)

@generated can only use type information (e.g. the dimensionality of your array) but not runtime values (such as a number):

help?> @generated
  @generated f

  @generated is used to annotate a function which will be generated. In the body of
  the generated function, only types of arguments can be read (not the values). The
  function returns a quoted expression evaluated when the function is called. The
  @generated macro should not be used on functions mutating the global scope or
  depending on mutable elements.

  See Metaprogramming for further details.

  Examples
  ≡≡≡≡≡≡≡≡

  julia> @generated function bar(x)
             if x <: Integer
                 return :(x ^ 2)
             else
                 return :(x)
             end
         end
  bar (generic function with 1 method)
  
  julia> bar(4)
  16
  
  julia> bar("baz")
  "baz"

Can you provide details about your specific problem? Using @nloops is not quite recommended.
Also see this blog post by Tim Holy

2 Likes

I know that @nloops is not recommended and used extensively Tim Holy’s blog concepts.

The basic objective here is to create one at a time all possible different arrays val of length 2n where each element satisfies some constraints compared to the others and has a different lower bound

B1<val[2n]<B2
lb_i  < val[i] < val[i+1]   i = 2 ... 2n-1
val[1]=C

and on each of those arrays perform some work and return a value.

Of course, I could iterate over the full space and just ignoring the unnecessary parts, but later the 4 will be replaced by 6, 8 maybe even 14…

Maybe an iterator could be constructed, but that does not simple neither for such a case ?

From a practical point of view, if I can only use type information to “pass the information”, I just could define an array at the calling level just for that purpose ? I would be happy with that.

Also it is a one shot program, not to be included into any module, I just want to solve one specific problem with it.

So actually if passing the information is too complicated, I could just change the parameter by hand.

How about taking a step back? Forget about generated functions, macros and metaprogramming. Just describe your problem, so we could give you the right solution.

1 Like

Tried to clarify the previous post. Basically visit a subspace of a hypercube to to some work on each of these points. And the subspace is becoming smaller and smaller compared to the hypercube when the dimension goes up.

I mean one dirty workaround would be to work with something like:

for i in 1:10
     do_something(Val(i))
end

This lifts the number to the type domain but it will result in (slow) dynamic dispatch since Val(i) is in the type domain but it’s type is runtime depending on the for loop. Slow of course can be relative.

But @generated should be able to work with it. I think you need to figure out how the exact syntax with @generated and @nloops combines.

Again to repeat: This is not really Julia idiomatic and CartesianIndex iterators would be more clear. Perhaps also some sort of recursive algorithms.

In the meanwhile I tried indeed a recursive approach which works fine.

2 Likes