Using ```@nall``` or ```@nexpr ```inside of ```@nif``` (from Base.Cartesian package)

Hi, I’m trying to do some meta-programming and have hit a wall. I’ve reduced my problem to what I think is a MWE containing the important aspects of the larger problem. It revolves around trying to use @nall (or, alternatively, @nexpr) inside @nif from the Base.Cartesian package, and I couldn’t find any other resources on if/how this might work.

NB: The way the code is written might seem silly as it can be achieved in other easier ways, but I believe that this general structure is required for the non-stripped-down version of my problem. Feel free to point out easier/better ways of achieving the MWE as it might be useful for others (or me!) to learn, or to comment on this problem in general and other potential solutions, but with fair warning that they may not end up being accepted as answers. Importantly, it is required that we include operators and the operator index as arguments (rather than hard-coding in some way) as the full use-case will replace the ‘multiplication’ step with potential recursive calls to this function.

The MWE problem
We have a collection of operators in the form of a vector of 2-tuples, where each entry is a (funciton, int) pair. The function is the actual operator, and the int is the ‘arity’ (ie the number of inputs that operator takes). This list of operators is unknown (length and content) until runtime.
I want to generate a function which takes the following inputs:

  • multipliers: a vector of ints of the same length as whatever operator is being applied. These will be used to multiply the x before the operator is applied
  • X: a vector/matrix/general data-structure of arbitrary length, each element of which we will apply the multiplied/summed operator to
  • op_idx: an integer in {1,…,length(operators)} which tells us which element of the list of operators to apply
  • operators: the list of operators itself

The function should check the value of op_idx to apply the relevant operator, and calculate for each x in X the value of:
op_fun(multipliers[1] * x, ..., multipliers[op_arity] * x),
where I use op_fun as shorthand for operators[op_idx][1] (the actual function) and op_arity as shorthand for operators[op_idx][2] (the arity of the function).
(Note that this requires multipliers to have length equal to the arity of whatever operator we are asking for; we can assume that the caller is clever enough to check this).

Proposed (incorrect) MWE solution

# Useful definitions
operators = [(+, 3), (*,2)]
counttuple(::Type{<:NTuple{N,Any}}) where {N} = N
function get_numops(::Type{Vector{T}}) where {T}
    return counttuple(T)
end

# Proposed generating code
@generated function apply_operator(multipliers::Vector{<:Integer}, cX::Vector{T}, op_idx::Integer, operators::Vector) where{T<:Number}
    n_op=get_numops(operators)
    return quote
        return Base.Cartesian.@nif(
            $n_op,
            i -> i == op_idx,
            i -> (op = operators[i];
                if (Base.Cartesian.@nall operators[i][2] j->(multipliers[j]>0))
                    print("Case 1")
                else
                    Base.Cartesian.Base.@nexprs(
                        op[2];
                        j -> result_j = cX * multipliers[j]
                    )
                    op[1]((Base.Cartesian.@ntuple op[2] j->result_j.x)...)
                end
            )
        )
    end
end

# Try and use it
multipliers=[1,3]
X_arr=[1,2,3,4]
apply_operator(multipliers, X_arr, 2, operators)

The error
This gives me an error

ERROR: LoadError: MethodError: no method matching var"@nall"(::LineNumberNode, ::Module, ::Expr, ::Expr)

Closest candidates are:
  var"@nall"(::LineNumberNode, ::Module, ::Int64, ::Expr)
   @ Base cartesian.jl:169

which I am interpreting as being a problem with trying to put @nall inside of @nif. Is there any way of fixing this without refactoring the code too much? I realise that ‘too much’ is subjective so welcome suggestions.

I think the first argument to @nall needs to be a compile-time constant (e.g. something that can be inferred from a type, like the dimensionality of an array).

Why are you trying to use a @generated function and a macro for this? Why not just write a loop?