Question about scoping inside a function


#1

Below are two functions that are slightly different in the names of the functions defined in them:

function f()
    h(x) = x
    b(x) = x
    q(x::Vector{Float32}) = foo(h, b)
end

function g()
    h(x) = x
    b(x) = x
    h(x::Vector{Float32}) = foo(h, b)
end

f()
g()

The call to g fails with UndefVarError: b not defined I think I am missing some scoping rules because both of the functions work fine if I just paste their bodies in the REPL.


#2

It’s not related to scope. It’s a lowering bug similar (/dup of) https://github.com/JuliaLang/julia/issues/18621


#3

This works:

julia> function g()
           h(x) = x
           b(x) = x
           (::typeof(h))(x::Vector{Float32}) = foo(h, b)
           h # this is necessary as above line does not evaluate to the function
       end
g (generic function with 1 method)

julia> g()
(::h) (generic function with 2 methods)

Related: This is also what is needed to add a method to a function passed into a function:

julia> f1(f) = f(x) = 1
ERROR: syntax: cannot add method to function argument f

julia> f1(f) = (::typeof(f))(x) = 1
f1 (generic function with 1 method)

julia> ff() = 1
ff (generic function with 1 method)

julia> f1(ff)

julia> ff(2)
1

#4

Thanks mauro, I will try it.

To give some context, this is what I am doing: https://github.com/JuliaDiff/ForwardDiff.jl/pull/165

I basically want to add a method to a user defined function with a macro. Both the user defined function and the macro should be able to itself be executed within a function.


#5

That be excellent to have! Looking at the example, probably better like so then:

julia> f2{F}(f::F) = ((::F)(x) = 1; f(5) )
f2 (generic function with 1 method)

julia> Base.Test.@inferred f2(ff)
WARNING: Method definition ff(Any) in module Main at REPL[1]:1 overwritten at REPL[5]:1.
1

#7

Still having some problem. Being a bit more explicit now:

In global scope, the following works fine:

using ForwardDiff

import ForwardDiff: value, partials, Dual

h(x) = 3*x
dh(x) = (println("Called"); 3 * one(x))

h(x::Dual) = Dual(h(value(x)), partials(x) * dh(value(x)))
ForwardDiff.derivative(h, 2.0)

This has the output:

julia> ForwardDiff.derivative(h, 2.0)
Called
3.0

which shows that my custom derivative is being called. However, for automatic differentiation, users frequently use closures.

My attempt as in the OP was:

function closure(a)
    h(x) = a*x
    dh(x) = (println("Called"); a * one(x))

    h(x::Dual) = Dual(h(value(x)), partials(x) * dh(value(x)))
    ForwardDiff.derivative(h, 2.0)
end

As been said, this fails with UndefVarError: dh not defined. (This just feels like a straight up bug, dh is defined!).

Just copy pasting the function bodies into the ::Dual function works (but this is of course not really user friendly):

julia> function closure2(a)
          h(x::Dual) = (println("Called"); Dual(a * value(x), partials(x) * a * one(value(x))))
          ForwardDiff.derivative(h, 2.0)
        end
closure2 (generic function with 1 method)

julia> closure2(1.0)
Called
1.0

julia> closure2(2.0)
Called
2.0

Trying to do it @mauro3 style:

function closure3(a)
    h(x) = a*x
    dh(x) = (println("Called"); a * one(x))

    (::typeof(h))(x::Dual) = Dual(h(value(x)), partials(x) * dh(value(x)))
    ForwardDiff.derivative(h, 2.0)
end

Doing it like this, the custom derivative function is actually never called. Moreover this leads to method redefinition warnings when calling it multiple times.

julia> closure3(2.0)
2.0

julia> closure3(2.0)
WARNING: Method definition (::Main.#h#15{Float64})(ForwardDiff.Dual) in module Main at REPL[3]:5 overwritten at REPL[3]:5.
2.0

I’m kinda out of ideas now. It feels like this should just work.


#8

Part of this certainly seems like a bug. Here a more minimal repo:

julia> function cl(a)
       g(x) = a*x
       h(x) = 7
       (::typeof(g))(x::Int) = (println("here"); g(Float64(x)) + h(x))
       sin(g(5))
       end
cl (generic function with 1 method)

julia> cl(5)
-0.13235175009777303 # this is sin(25)

julia> function cll(a)
       g(x) = a*x
       h(x) = 7
       (::typeof(g))(x::Int) = (println("here"); g(Float64(x)) + h(x))
       g
       end
cll (generic function with 1 method)

julia> gg = cll(5)
(::g) (generic function with 2 methods)

julia> sin(gg(5))
here
0.5514266812416906

I would expect those to be the same.


#9

This is #265 but it will also be the expected behavior after #265 is fixed.


#10

Why is this #265? I just want to add a method to a function. Everything works as I want it in global scope.


#11

Here Jeff states: “It’s an increasingly official rule that method changes aren’t guaranteed to take effect until you return to the top level (see e.g. #4688).” I think that is what’s hit here.

Also somewhat related to: https://github.com/JuliaLang/julia/issues/11808.
Although translating above to match Jeff’s comment, does not lead to unexpected behavior:

julia> let
       g(x) = (println("here 1"); pi/2)
       h(x) = pi/2
       # the typeof trick is needed here too:
       (::typeof(g))(x::Int) = (println("here 2"); g(Float64(x)) + h(x)) 
       sin(g(5))
       end
here 2
here 1
1.2246467991473532e-16

I’m not sure how that is all reconcilable with above quote by Jeff. I would have thought the last code snippet shouldn’t work either.


#12

I don’t see what is different with what I am trying to do and the following:

function f()
    g(x::Int) = println("Here 1")
    g(x::Float64) = println("Here 2")
    g(1)
    g(2.0)
end
julia> f()
Here 1
Here 2

#13

To trigger the odd behavior, you need to call a method (or function?) which was also defined inside the outer function (h above). I guess the key part of Jeff’s statement is “guaranteed”: method updates may take effect or may not, i.e. undefined behavior. Nonetheless, I think we should file an issue for this. You or I?

Concerning your problem: it does seem a bit odd to define, evaluate and throw away generic functions within a method. Is there no other way to do what you want?

Otherwise, you could do something like this:

julia> function cl(a)
              global g(x) = a*x
              global h(x) = 7
              # the typeof trick is not needed:
              g(x::Int) = (println("here"); g(Float64(x)) + h(x))
              sin(g(1))
              end
cl (generic function with 1 method)

julia> cl(1)
here
0.9893582466233818

I think you’re generating this with macros, thus you could use gensym to avoid name-collisions.


#14

Isn’t this what any closure defined in a function is though? Defined, evaluated and thrown away.
Is the conclusion here that multiple dispatch is not allowed for closures created inside a function?

What I want is for the user, at any point, to give me a function h(x::Vector) and I then want to create the method h{T <: ForwardDiff.Dual}(x::Vector{T}) such that function will be called for eltypes of ForwardDiff.Dual. In the new method I need to be able to call the original h(x::Vector) function.


#15

It’s very different since using (::typeof(g))(...) = ... you are adding a method at runtime.


#16

But the only reason for that is because the original code which did not use typeof did not work and threw an error about an undefined variable which actually is defined.

Again:

function cll(a) # works
    g(x) = a*x
    g(x::Int) = g(Float32(x))
    g(3)
end

function cll2(a) # doesnt work
    g(x) = a*x
    h(x) = 2.0
    g(x::Int) = (g(Float32(x)), h(x))
    g(3)
 end

#17

As I said in the very beginning, this is a lowering bug.

The “work around” does not do the same thing as the original code.


#18

Ok, thank you. Is there a workaround that does the same (or with the same intention) as the original code.


#19

define h before the first g or after the second g might be ok.


#20

Oh, defining h before g seems to work! Not super user friendly but better than nothing. Thanks!


#21

Only problem now is that g turns into a Box.