Julia - Metaprogramming for using several modules

Viewed 99

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.

1 Answers

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
Related