Search code examples
juliaevalmetaprogramming

Iterating over different functions with different number of parameters in Julia


I'm trying to run a loop over different functions with different number of arguments. The variables are created at runtime inside the loop, and I want to use eval at each iteration to instantiate a Struct using the variable :symbol. However, I can't do this since eval only works in the global scope. This is the MWE for the case that works:

function f1(x); return x; end
function f2(x1,x2); return x1+x2; end

handles = [f1,f2]
args =[:(x1),:(x1,x2)]

x1 = 1; x2 = 1;
for (i,f) in enumerate(handles)
    params = eval(args[i])
    @show f(params...)
end

f(params...) = 1
f(params...) = 2

However, if I move the variable definitions inside the loop, which is what I actually want, it doesn't work after restarting Julia to clear the workspace.

function f1(x); return x; end
function f2(x1,x2); return x1+x2; end

handles = [f1,f2]
args =[:(x1),:(x1,x2)]

for (i,f) in enumerate(handles)
    x1 = 1; x2 = 1;
    params = eval(args[i])
    @show f(params...)
end

ERROR: UndefVarError: x1 not defined

I've tried several of the answers, such as this one, but I can't seem to make it work. I could write a custom dispatch function that takes[x1,x2] and calls f1 or f2 with the correct arguments. But still, is there any way to do this with eval or with an alternative elegant solution?

EDIT: here are more details as to what I'm trying to do in my code. I have a config struct for each algorithm, and in this I want to define beforehand the arguments it takes

KMF_config = AlgConfig( 
    name = "KMF",
    constructor = KMC.KMF,
    parameters = :(mu,N,L,p),
    fit = KMC.fit!)
MF_config = AlgConfig( 
    name = "MF",
    constructor = KMC.MF,
    parameters = :(mu,N,L),
    fit = KMC.fit!)

alg_config_list = [KMF_config, MF_config]
for (i,alg_config) in enumerate(alg_config_list)
    mu,N,L,p,A,B,C,D,data = gen_vars() #this returns a bunch of variables that are used in different algorithms
    method = alg_config.constructor(eval(method.parameters)...)
    method.fit(data)
end

One possible solution is to have a function take all the variables and method, and return a tuple with a subset of variables according to method.name. But I'm not sure if it's the best way to do it.


Solution

  • Here's an approach using multiple dispatch rather than eval:

    run_a(x, y) = x + 10*y
    run_b(x, y, z) = x + 10*y + 100*z
    
    extract(p, ::typeof(run_a)) = (p.x, p.y)
    extract(p, ::typeof(run_b)) = (p.x, p.y, p.z)
    genvars() = (x=1, y=2, z=3)
    
    function doall()
        todo = [
            run_a,
            run_b,
        ]
        for runalg in todo
            v = genvars()
            p = extract(v, runalg)
            @show runalg(p...)
        end
    end
    

    In your example you would replace run_a and run_b with KMC.KMF and KMC.MF.

    Edit: Cleaned up example to avoid structs that don't exist in your example.