Hi! I am trying to extend the methods of a collection of functions programmatically with no partial success.
foo(x::Real) = x
for f in (:foo,)
@eval ($f)(x::Complex) = x'
end
function fun()
foo(x::Real) = x
for f in (:foo,)
@eval ($f)(x::Complex) = x'
end
return foo
end
bar = fun()
methods(foo)
# 2 methods for generic function "foo":
[1] foo(x::Real) in Main at /home/cedricmc/Dropbox/Documentos/matematicas/articulos/2020-variational-accelerated-methods/simulations/test_eval.jl:1
[2] foo(x::Complex) in Main at /home/cedricmc/Dropbox/Documentos/matematicas/articulos/2020-variational-accelerated-methods/simulations/test_eval.jl:9
methods(bar)
# 1 method for generic function "foo":
[1] (::var"#foo#1")(x::Real) in Main at /home/cedricmc/Dropbox/Documentos/matematicas/articulos/2020-variational-accelerated-methods/simulations/test_eval.jl:7
I would expect that foo and bar have the same methods, however the complex method for bar is ignored. Why? I guess it is a scope problem. How to solve this so bar be like foo?
I have updated the script with something more eloquent.
foo(x::Real) = x
f = :foo
eval("$f(x::Complex) = x'")
display(foo)
display(eval(f))
function fun()
goo(x::Real) = x
g = :goo
eval("$g(x::Complex) = x'")
display(goo)
display(eval(g))
return goo
end
bar = fun()
foo (generic function with 1 method)
foo (generic function with 1 method)
(::var"#goo#1") (generic function with 1 method)
ERROR: LoadError: UndefVarError: goo not defined
The first eval within fun() triggers no errors, but the second, display(eval(g)), does trigger an UndefVarError, which to me makes no sense.
You’re returning a local variable goo. It’s not associated with the global symbol :goo. However, eval always runs in global scope and creates the only function associated with the global symbol :goo.
It’s not associated with the global symbol :goo.
Why do you say “global”? There is no global symbol :goo.
However, eval always runs in global scope and creates the only function associated with the global symbol :goo.
Had the first eval run in global scope, a function called goo in global scope should be defined for complex numbers, however no function goo is created in global (or local) scope, hence the error of the second display.
Using quoting, eval(:($g(x::Complex) = x')), or macro, @eval $g(x::Complex) = x', behaves as you say.
Every eval runs in global scope, and eval’ing a string does not define a function, it merely evals the string. Your first example, with the foo functions suffers from this. A slight variation shows what happens:
foo(x::Real) = x
for f in (:foo,)
@eval ($f)(x::Complex) = x'
end
function fun()
foo(x::Real) = x
for f in (:foo,)
@eval ($f)(x::Rational) = x'
end
return foo
end
bar = fun()
methods(foo)
yields
(::var"#foo#5") (generic function with 1 method)
# 3 methods for generic function “foo” from Main:
[1] foo(x::Complex)
@ REPL[2]:2
[2] foo(x::Rational)
@ REPL[3]:4
[3] foo(x::Real)
@ REPL[1]:1
This shows that the eval inside a function runs in global scope, and does not add methods to the local function foo.
It is similar to my second example but more clarifying, thx again.
Coming back to my initial post, how can I create a function within a function, specify it programmatically and return it?
function fun()
foo(x::Real) = x
f = :foo
@eval $f(x::Complex) = x'
return foo
end
bar = fun()
varinfo()
name size summary
–––––––––––––––– ––––––––––– ––––––––––––––––––––––––––––––––––––––––––––––––
Base Module
Core Module
InteractiveUtils 307.688 KiB Module
Main Module
bar 0 bytes (::var"#foo#1") (generic function with 1 method)
foo 0 bytes foo (generic function with 1 method)
fun 0 bytes fun (generic function with 1 method)
I would like bar to have two methods, the ones that currently have foo and bar itself.
test() = begin
@eval foo(x::Real) = x
@eval foo(x::Complex) = x
end
test()
varinfo()
yielding
name size summary
–––––––––––––––– ––––––––––– –––––––––––––––––––––––––––––––––––––
Base Module
Core Module
InteractiveUtils 331.024 KiB Module
Main Module
foo 0 bytes foo (generic function with 2 methods)
test 0 bytes test (generic function with 1 method)
within a single function call, using @eval to create a new method and then trying to immediately call that method will I think not work, because of world age.
You can generate an “anonymous” name with gensym() and define various methods with eval:
function fun(x)
nm = gensym()
T = widen(Complex{Float32})
y = x^2
@eval begin
$nm(x::Real) = x
$nm(x::$T) = x' + $y
end
end
bar = fun(2.0)
methods(bar)
bar(1)
bar(1.0+im)
yields the output
##295 (generic function with 2 methods)
# 2 methods for generic function “##295” from Main:
[1] var"##295"(x::Real)
@ REPL[9]:6
[2] var"##295"(x::ComplexF64)
@ REPL[9]:7
1
5.0 - 1.0im
But take note of the “world age” problem that was referenced above, new methods/functions are not immediately visible.
by “immediate” i mean within the same function call where it was defined.
julia> test() = begin
@eval foo(x::Real) = x
@eval foo(x::Complex) = x
foo(1+1im) # will error
end
test (generic function with 1 method)
julia> test()
ERROR: MethodError: no method matching foo(::Complex{Int64})
The applicable method may be too new: running in world age 32433, while current world is 32435.
Closest candidates are:
foo(::Complex) at REPL[1]:3 (method too new to be called from this world context.)
foo(::Real) at REPL[1]:2 (method too new to be called from this world context.)
Stacktrace:
[1] test()
@ Main ./REPL[1]:4
[2] top-level scope
@ REPL[2]:1
function fun(x)
T = widen(Complex{Float32})
y = x^2
@eval let foo = x::Real -> x
(::typeof(foo))(x::$T) = x' + $y
foo
end
end
bar = foo(2.0)
However, the only difference I’ve noticed is that the output of methods(bar) states that it is an anonymous function, not a generic function. I don’t know the difference.
Can you step back and tell us what you are trying to accomplish? Trying to @eval in a function is usually an indication that you made a wrong turn somewhere.
The usual way to create new functions inside a function (i.e. a higher-order function) is to return a closure (an anonymous function), not to do code generation / metaprogramming.