Update source file with eval-resulted code

Imagine the following scenario:

I have a function that generates an Expr, which is in turn processed by eval.

To make it easier, let’s create a small example:

function makeval()
    ex =
        Expr(:function,
            Expr(:call, :addxy,
                Expr(:(::), :x, :Int),
                Expr(:(::), :y, :Int)),
            Expr(:block,
                Expr(:call, :+, :x, :y)))
    eval(ex)
end

@info "Main names: " names(Main, all=false)
# will print:
#┌ Info: Main names: 
#│   names(Main, all = false) =
#│    4-element Vector{Symbol}:
#│     :Base
#│     :Core
#│     :Main
#└     :makeval

makeval()

@info "Main names: " names(Main, all=false)
# will print:
#┌ Info: Main names: 
#│   names(Main, all = false) =
#│    5-element Vector{Symbol}:
#│     :Base
#│     :Core
#│     :Main
#│     :addxy
#└     :makeval

@info "1 + 2 = " addxy(1, 2)
# will print:
#┌ Info: 1 + 2 = 
#└   addxy(1, 2) = 3

Now, it is obvious that I could just do something with string(ex) inside the function - and save it to some file if I wanted.

My question is this: is there an idiomatic way to save a snapshot of the current state (e.g., source code) of a module?

In my example, before executing the makeval function, the addxy function was not defined. However, after makeval call, addxy is my module scope and I can call it.

Don’t spend much time with the answer (OK, you can go into details if you really want) - but if there are a set of functions to do this, just point me in the right direction and I can figure it out and I can publish here how it went (for future reference).

1 Like

It’s not very what you’re end goal is, but you usually don’t want to call eval directly. Instead you could use a macro:

macro make()
    return quote
        function $(esc(:addxy))(x::Int, y::Int)
            x + y
        end
    end
end

You have to escape addxy to make it visible outside the macro, but otherwise this has the same effect as your function example.

julia> @info("Main names :", names(Main, all=false))
┌ Info: Main names :
│   names(Main, all = false) =
│    7-element Vector{Symbol}:
│     Symbol("@make")
│     :Base
│     :Core
│     :InteractiveUtils
│     :Main
│     :addxy
└     :ans

julia> @make()
addxy (generic function with 1 method)

julia> @info("Main names :", names(Main, all=false))
┌ Info: Main names :
│   names(Main, all = false) =
│    7-element Vector{Symbol}:
│     Symbol("@make")
│     :Base
│     :Core
│     :InteractiveUtils
│     :Main
│     :addxy
└     :ans

julia> @info "1 + 2 = " addxy(1, 2)
┌ Info: 1 + 2 = 
└   addxy(1, 2) = 3

Not necessarily, like if the Expr contains something other than symbols, literals, or such Expr:

julia> x = []
Any[]

julia> eval(:(function f() $x, $x end))
f (generic function with 1 method)
julia> f()[1] === f()[2]
true

julia> eval(Meta.parse(string(:(function f() $x, $x end))))
f (generic function with 1 method)
julia> f()[1] === f()[2]
false

Thanks for pointing that out. Let’s keep things in the scope of the MWE: I can do string(ex) where ex is defined as in the MWE, not where ex is any Expr.