Search code examples
juliametaprogramming

Julia - Metaprogramming for using several modules


I'm using Julia to autograde students' work. I have all of their files Student1.jl, Student2.jl, etc. as separate modules Student1, Student2, etc in a directory that is part of LOAD_PATH. What I want to be able to do works completely fine in the REPL, but fails in a file.

macro Student(number)
    return Meta.parse("Main.Student$number")
end

using Student1
@Student(1).call_function(inputs)

works completely fine in the REPL. However, since I'm running this in a script, I need to be able to include the modules with more metaprogramming that is currently not working. I would have thought that the exact same script above would have worked in a file Autograder.jl by calling

@eval(using Student1)
@Student(1).call_function(inputs)

in a module Autograder. But I get either an UndefVarError: Student1 not defined or LoadError: cannot replace module Student1 during compilation depending on how I tweak things.

Is there something small in Julia metaprogramming I'm missing here to make this autograding system work out? Thanks for any advice.


Solution

  • The code just as you have written works for me on julia versions 1.1.0, 1.3.1, 1.5.1, 1.6.0 and 1.7.0. By that I mean, if I add an inputs variable and put your first code block in a file Autograder.jl and run JULIA_LOAD_PATH="modules:$JULIA_LOAD_PATH" julia Autograder.jl with the student modules in the modules directory I get the output of the call_function function in the Student1 module.

    However if Autograder.jl actually contains a module then the Student$number module is not required into Main and your macro needs to be modified accordingly:

    module Autograder
    macro Student(number)
        return Meta.parse("Student$number") # or "Autograder.Student$number"
    end
    
    inputs = []
    @eval(using Student1)
    @Student(1).call_function(inputs)
    end
    

    Personally I wouldn't use a macro to accomplish this, here is one possible alternative:

    student(id) = Base.require(Main, Symbol("Student$(id)"))
    let student_module = student(1)
        student_module.call_function(inputs)
    end
    

    or without modifying the LOAD_PATH:

    student(id) = include("modules/Student$(id).jl")
    let student_module = student(1)
        student_module.call_function(inputs)
    end