Julia scoping: why does this function modify a global variable?
Asked Answered
B

1

3

I'm a relative newcomer to Julia, and so far I am a fan of it. But coming from years of R programming, some scoping rules leave me perplexed.

Let's take this function. This behaves exactly as I would expect.

function foo1(x)
    y = x
    t = 1
    while t < 1000
      t += 1
      y += 1
    end
    return 42
end

var = 0;
foo1(var)
# 42
var
# 0

But when doing something similar on an array, it acts as a mutating function (modifies it argument in the global scope!)

function foo2(x)
    y = x
    t = 1    
    while t < 1000
      t += 1
      y[1] += 1
    end
    return 42
end

var = zeros(1);
foo2(var)
# 42
var
# 999.0

I realize I can fix this by changing the first line to y = copy(x) , but what is the reason for such a (dangerous?) behaviour in the first place?

Beamon answered 23/6, 2020 at 12:20 Comment(1)
This behavior is unrelated to variable scope, it's about the fact that mutation and assignment are not the same: #33003072Antimonyl
L
7

I would write an answer to this, but I think John Myles White has already done it better than I ever could so I'll just link to his blogpost:

https://www.juliabloggers.com/values-vs-bindings-the-map-is-not-the-territory-3/

In short x = 1 and x[1] = 1 are very different operations. The first one is assignment—i.e. changing a binding of the variable x—while the second is a syntactic sugar for calling the setindex! function, which, in the case of arrays, assigns to a location in the array. Assignment only changes which variables refer to which objects and never modifies any objects. Mutation only modifies objects and never changes which variables refer to which objects. This answer has a bit more detail about the distinction: Creating copies in Julia with = operator.

Lewd answered 23/6, 2020 at 12:45 Comment(6)
in short x = 1 and x[1] = 1 are very different operations. The first one is assignment (changing a binding of x) while the second is a syntactic sugar for setproperty! function.Donata
thank you for the pointer, quite enlightening indeed. I'm surprised by the post's tone, which seems to imply that modifying a function's arguments is desirable. I guess I need to forget about functional programming ("no side effects") in Julia and embrace the efficiency.Beamon
The convention is that if a function mutates its arguments the function name should end with ! to warn the user of that. It is common for functions to have mutating and purely functional variants to allow efficiency when necessary but offer safer, non-mutating behavior by default.Antimonyl
Great, thank you for clarifying. Is the right way to write safe, functionally pure functions in Julia then to first copy() the arguments that might be modified in the function's body?Beamon
If you have an algorithm that is easiest to express and/or most efficient to perform in-place, then do that! Define it as a function algorithm!(x) ... end. You can then define a secondary algorithm(x) = algorithm!(copy(x)) if that makes sense.Godsey
Incidentally yesterday I have made a blog post showing this strategy of having two functions, if you would be interested have a look here bkamins.github.io/julialang/2020/06/22/ar1.html. There you can see the performance differences between them.Donata

© 2022 - 2024 — McMap. All rights reserved.