A question about redefining a function in Julia

Hello. I just tested the following small code on Julia, and found that the value g() is changed after redefining the function f, but c is unchanged. I was wondering the reason behind this difference, and how to keep g() unchanged as well. Thanks.

f(x)=x+1
c=f(1)  #c=2
g()=f(1)  #g=2
f(x)=x+2
c  #c=2 unchanged
g()  #g=3 changed

g() calls the function f with 1 as argument.
If you change f to output argument + 2 instead of argument + 1, g() will use your changed function f.

Yes, I understand. But why doesn’t the value of c change as well?

I think it’s just because c refers to the value 2, which was computed by evaluating the expression (as it was defined at that time) f(1), while g is looking up whatever the current definition of f is when you call it, and then executing.

4 Likes

c does not change because it took the value of f(1) before you changed the meaning of function f. g does change, because it calls function f. This allows you to have all sorts of functions and not having to worry to recompile all functions that use your changed f down the line, just recompile f.

1 Like

I think so. But why c took the value of f at the time it was defined while g needed to look up the definition of f? c and g() were defined in the same way. Any mechanism behind it?

Functions run code. Assignments simply give names to values.

While f() = x and f = x look similar, they are wildly different things. The former defines a function that can be run later, whereas the latter gives another name to whatever value x is at that point.

10 Likes

Yes, but c and g() were defined in the same way, why do they behave differently when calling their values? Any mechanism about it?

explained by @mbauman just before you asked :slightly_smiling_face:

If you want your function be be more flexible, instead of redifining it every time, you could define it as follows:

f(x,y) = x + y
g(x) = f(3,x+2)   
# example, note the x is local to the function, 
# so the y in f becomes the value of x+2

julia>  c = f(1,1)   # c=2
julia>  c = f(1,2)   # c=3
julia>  c = g(2)    # c=7 

although I would not bother if the function is not more than one + etc. operator.

Thanks. This clarifies my confusion. By the way, is there any documentation related to the delayed run of functions?

Yes, the Manual page titled Functions. It’s also how functions work in programming generally, by whatever name in specific languages.

3 Likes

I return to this blog post often, and recommend it a lot. Somehow, it just really cemented understanding/intuition of the way things work

1 Like

There is really not much to say about that. A function runs its content when it is called, not when it’s defined. Take e.g. the definition:

function myprint(x) 
    print("DEBUG: ")
    println(x)
end

The whole point with functions is that the text DEBUG: and the textual representation of the value of x is printed whenever you exectute a myprint(whatever) in your program. Not when the function myprint is defined.

I feel like the kwarg (not belongs to content) concerns its defining scope

julia> function f(; x = x)
           println(x)
       end;

julia> x = 0;

julia> function test()
           x = 1
           f()
       end;

julia> test()
0

The manual is a bit vague on which scope the default values are evaluated in. With a bit of good will it can be surmised that the scope consists of the previous arguments and the scope where the function was defined.

It wouldn’t really make sense to use the caller’s scope. The writer of the function would lose all control, and the caller could inadvertently change the defaults by defining a variable which happens to be used in the defaults.

julia> function f(; x = x/2, y = x)
           @show x y    
       end
f (generic function with 1 method)

julia> f();
ERROR: UndefVarError: `x` not defined in `Main`

julia> x = 1;

julia> f();
x = 0.5
y = 0.5

julia> f(x=2);
x = 2
y = 2
1 Like

I totally don’t understand this behavior:

First I think f(x=2) is awful grammar which doesn’t make sense (I’m surprised it is supported). It would be clear to write f(; x=2).

Second, I think the intention of function f(; y = x) is:

  • the inner y depends on the x in the outer scope.

So if the outer x is 1, then the outcome of the last line should be y = 1.

But, whatever, I probably won’t write code like that…

I’m very confused about what is confusing here. Keyword defaults and positional defaults work exactly the same way. Default expressions, like everything else about a function definition, are evaluated in the scope where the function is defined.

I think the confusion is about why g()=f(1) do not store the definition of f at the time of evaluation, just like c=f(1) does. g() is affected by redefining f, c does not.

It shouldn’t store, because that’s excessive work that would slow our program.

function f(x)
    x + 1
end
function g()
    f(1)
end

This style merely says that g captures the name f.
But then we redefines

function f(x)
    x + 2
end

so the name f is re-bind to a new function “add 2”. So the final outcome is

julia> g()
3

I’m unaware of the existance of such a scope, to be honest.

I think writing expressions at the default value position of a kwarg is obfuscating. So I mentioned “I probably won’t write code like that”.

Yes, I agree with the current design, I was just talking about the confusion about the confusion.

In addition, having several versions of functions creates harder to follow programs.