Allow redefining functions names of outer scope? ERROR: UndefVarError: f not defined

question

#1

It seems we have to use reflections in order to overwrite a function from an outer scope.

Please look at the following minimal example

module MyModule
  f(a) = a

  function f2()
    h = getfield(MyModule, :f)
    #= if you uncomment the following
    you get an `ERROR: UndefVarError: f not defined` 
    pointing to exactly this assignment `h = f` =#

    # h = f  # PLEASE UNCOMMENT THIS
    f(args...; kwargs...) = h(2, args...; kwargs...)
    f()
  end
end

MyModule.f2()  # 2

If we know the outer scope is just module, we can use simple getfield(MyModule, :myfunc) to refer to the function which we want to overwrite locally (as used in the example), however if you have nested functions I even don’t know a way to do that.

Is this restriction needed? or could julia just correctly infer that the first f in h = f is pointing correctly to the outer scope?

I am using julia 1.1


#2

If I understand you correctly, what are you looking for is Revise workflow.


#3

Assigning to a name from inside a function causes it to be a local variable. If you want it to be global you have to declare it global. And you can’t have it both ways: the name f is either local or global.


#4

Is there a design decision which explains why this is done?

It looks so unintuitive and unneeded to me


#5

Please be specific about what you have an issue with: the idea of scoping, the distinction between local and global, or the fact that these are disjoint categories for specific variables?

Also, note that scoping rules are a very fundamental building block of the language, and have usually received a lot of attention. This is also the case for Julia. When you are learning a new language, consider the possibility that you may need to invest in understanding how it works before labeling fundamental features “unneeded”.


#6

that is exactly what I am doing right here - trying to understand why it is needed.

But apparently that is not easy to understand, at least no one could help so far (of course some answers already pointed out that julia currently is designed this way, but the more important question to me is to understand why)


#7

Again, it is unclear what you are referring to as a “design choice” — even though it is short, the example you included involves multiple ones. Reading about scoping would be a good start.

Also, instead of delving into the design of the language, it may be easier to just explain what you want, not how you tried to get it. Eg do you want the behavior of the API function f to depend on some global state, and have the function recompiled? Then

module MyModule

export f

_f(a) = a

f(a) = _f(a)

function toggle()
    @eval f(a) = 2 * _f(a)
end

end

could work, eg

julia> MyModule.f(1)
1

julia> MyModule.toggle()
f (generic function with 1 method)

julia> MyModule.f(1)
2

though I don’t think you would lose much by just having a global flag (as the above is not ideal coding style).


#8

my usecase is super similar to the simple example I gave above: I would like to fixate one parameter of a function, and use the same name for the new wrapped function.

It is that simple.

I can make a naming convention by just adding underscore to the name, but it looks so natural that the one f could refer to outer scope while the next f is the new wrapped function.


#9

It may help to think of functions in Julia as a table of methods. The f(...) = ... syntax adds to this table. The table itself may have global (usually) or local scope, eg

julia> function f(x)
           g(::Int) = 1
           g(::Float64) = 2
           g(x)
       end
f (generic function with 1 method)

julia> f(1)
1

julia> f(1.0)
2

julia> g(1)
ERROR: UndefVarError: g not defined
Stacktrace:
 [1] top-level scope at REPL[18]:1

I would just use a closure for pinning down a parameter. The details of the solution depend on where you would like to use the transformed function (ie inside or outside f2 and/or MyModule in your example).


#10

So the same name in the same scope would be global in some places but local in others? That seems confusing and also not how local scope works in any other languages I’m aware of. You could use a let block to introduce a new binding with the same name and still use the outer meaning in the definition.