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?

Levasco
  • 159
  • 9
  • 1
    This behavior is unrelated to variable scope, it's about the fact that mutation and assignment are not the same: https://stackoverflow.com/questions/33002572/creating-copies-in-julia-with-operator/33003055#33003055 – StefanKarpinski Jun 23 '20 at 14:54

1 Answers1

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.

StefanKarpinski
  • 32,404
  • 10
  • 86
  • 111
Nils Gudat
  • 13,222
  • 3
  • 39
  • 60
  • 3
    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. – Bogumił Kamiński Jun 23 '20 at 12:47
  • 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. – Levasco Jun 23 '20 at 13:19
  • 1
    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. – StefanKarpinski Jun 23 '20 at 14:57
  • 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? – Levasco Jun 23 '20 at 15:24
  • 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. – mbauman Jun 23 '20 at 15:53
  • Incidentally yesterday I have made a blog post showing this strategy of having two functions, if you would be interested have a look here https://bkamins.github.io/julialang/2020/06/22/ar1.html. There you can see the performance differences between them. – Bogumił Kamiński Jun 23 '20 at 17:03