26

The docs say

In Julia, all arguments to functions are passed by reference.

so I was quite surprised to see a difference in the behaviour of these two functions:

function foo!(r::Array{Int64})                                                                                                                                                                                     
        r=r+1                                                                                                                                                                                                      
end


function foobar!(r::Array{Int64})                                                                                                                                                                                  
        for i=1:length(r)                                                                                                                                                                                          
                r[i]=r[i]+1                                                                                                                                                                                        
        end                                                                                                                                                                                                        
end 

here is the unexpectedly different output:

julia> myarray
2-element Array{Int64,1}:
 0
 0

julia> foo!(myarray);

julia> myarray
2-element Array{Int64,1}:
 0
 0

julia> foobar!(myarray);

julia> myarray
2-element Array{Int64,1}:
 1
 1

if the array is passed by reference, I would have expected foo! to change the zeros to ones.

Lindon
  • 1,292
  • 1
  • 10
  • 21
  • 3
    The [docs](https://docs.julialang.org/en/v0.5/manual/arrays/) you were probably looking at have been [corrected](https://docs.julialang.org/en/v1/manual/arrays/) in more recent versions. They now state the correct evaluation strategy (pass by sharing) rather than pass by reference. – user2357112 Nov 17 '18 at 02:29

4 Answers4

32

r=r+1 is an Assignment statement, this means it reallocates r, so it no longer refers to its pair in the parent scope. but r[i]=r[i]+1 Mutates r value, mutation is differ from assignment (a good description here), and after that r still refers to its pair variable in the parent scope.

Community
  • 1
  • 1
Reza Afzalan
  • 5,646
  • 3
  • 26
  • 44
13

I think the document is a bit vague here.

Strictly speaking, Julia is "call-by-value where the value is a reference" , or "call-by-sharing", as used by most languages such as python, java, ruby, js... See wiki

A call by reference behaviour would indeed make foo! to change the zeros to ones. However Julia doesn't support that. (If you know C#, that is what ref or out does)

colinfang
  • 20,909
  • 19
  • 90
  • 173
  • 3
    I’ve also seen the term 'call-by-pointer' (or 'pass-by-pointer') used to refer to this, which I consider short and to the point. In any case, it’s always worth reminding that the terms are used sometimes for the purpose of language theory, sometimes for the purpose of practice. And those meanings don’t always coincide, or even differ within a discipline depending on author! – Luc Danton Feb 08 '16 at 00:59
  • "call-by-pointer-value" is much more clear because it avoids confusion particularly with C++ and Rust references. Arguably, calling Julia "pass by reference" is incorrect, because it is always pass-by-value, and if the argument is an object then the value is a pointer. C++, Rust, PL/SQL, Swift, (and I suppose technically C and assembler) have the ability to pass-by-reference, which is a distinct operation to passing a pointer to an object. Calling the object handles/pointers "references" at all introduces confusion. – theferrit32 Dec 04 '19 at 04:47
7

In order to mutate each variable inside an array, the broadcast . operation can be used. But be aware that each value inside the array will be changed equally thus there is no need for a for loop.

In the case of adding 1 to each element of an array :

a = rand(1:10, 10)
show(a) = [4, 8, 9, 1, 4, 2, 6, 7, 1, 5]

function add1!(a::Array{Int64})
    a .= a .+ 1
end

add1!(a);
show(a) = [5, 9, 10, 2, 5, 3, 7, 8, 2, 6]

Despite this, if each value of an array is needed to be changed independently then for loop with indices is inevitable.

Kadir Gunel
  • 309
  • 3
  • 14
3

In practice, regardless of theory(call by sharing), described in previous answer, everything happens in Julia as if pointer variables as arrays were passed by reference, while scalar variables, such as numbers, were passed by value.

This is a pain for people like me, used to C or Pascal languages, where one specify in formal parameter part in function declaration, if the parameter is by value or by reference.

However, due to Julia's feature that allows return of multiple values in a function, there is an elegant way to simulate parameters by reference for scalar variables. Obviously this works even for immutable variables like strings, since the variables are recreated.

Julia's Code

function process(a,b,c)
  a += c 
  b *= c
  return a*b*c, a, b  # normal result, changing "a" and "b"
end

a = 4
b = 7
println("Before: ", a," ",b)
result, a, b = process(a,b,7)
println("After: ", a," ",b," ", result)

Display

Before: 4 7
After: 11 49 3773 

a and b was both changed inside function process. a was added to 7 and b was multiplied by 7

Antonello
  • 6,092
  • 3
  • 31
  • 56
Paulo Buchsbaum
  • 2,471
  • 26
  • 29