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.