How to define a function such that its arguments can mutate?

A stupid question, I just want to have a function which is like a subroutine in Fortran.
Just a simple example,

function gauss(theta::Array{Float64,2})
theta = [1.0 1.0 ; 2.0 2.0]
return nothing
end

I just want to input an array theta, then its value needs to be changed to what I set theta = [1.0 1.0 ; 2.0 2.0]. However, when I do

a = [100.0 100.0 ; 200.0 200.0]
gauss(a)
a

I would expect a to be changed to [1.0 1.0 ; 2.0 2.0],
however, result is still a = [100.0 100.0 ; 200.0 200.0]. So a does not changed at all.

I also tried gauss!(a), but the result remain the same as if no ! is added.

Why?
How to change really mutate the arguments in this example, and in general?

 function gauss(theta::Float64, beta::Array{Float64,2}, gamma::Int64)
     theta = 1.0
     beta  = [1.0 2.0 ; 3.0 4.0]
     gamma = 50
 return nothing
 end
 a = 100.0
 b = [0.0 0.0; 0.0 0.0]
 c = 10000
 gauss(a,b,c)
 println(a,b,c)

In the abvoe example, after gauss(a,b,c), the println(a,b,c) still just give the initial values of a, b, c.

100.0[0.0 0.0; 0.0 0.0]10000

This is obviously not what I want.
as @odow pointed out, for array I can use .= , but how about for Float, Int variables?

Thanks in advance, and sorry for the stupid question!

You need to modify the elements of the array

function gauss(theta::Array{Float64,2})
    theta .= [1.0 1.0 ; 2.0 2.0]
    return nothing
end

Following the “variables are like named boxes” metaphor: the value of theta is a box, and theta is the name of the box, theta = [...] just makes the name theta point to a different box, it doesn’t modify the contents of the original box.

6 Likes

The ! for argument-mutating functions is only a naming convention. You as a developer name functions that way to let the user know that your function will have side-effects. It does not cause any change to the code you write.

6 Likes

Thank you very much!
May I ask, is there some relevant materials/links you could advise to read?

I have tried another example,

function gauss(theta::Float64)
theta = 1.0
return nothing
end
a = 100.0
gauss(a)
a

Above example, theta .= 1.0 does not work and give me an error, and the output a is still 100.0 instead of 1.0, how to solve the problem? Thanks a lot!

That example will not work because numbers are immutable.
A good resource to read is the official documentation. In particular, the frequently asked questions in the docs say:

In Julia, the binding of a variable x cannot be changed by passing x as an argument to a function. When calling change_value!(x) in the above example, y is a newly created variable, bound initially to the value of x , i.e. 10 ; then y is rebound to the constant 17 , while the variable x of the outer scope is left untouched.

However, if x is bound to an object of type Array (or any other mutable type). From within the function, you cannot “unbind” x from this Array, but you can change its content. For example:

You can also go the manual in the functions section, where it mentions that modifications to a mutable container will be visible from the outside.

Numbers are not a mutable container, so you cannot change their value from inside a function. What you could do if you want something like that is to use the Ref trick from one of your other posts. Another choice is to create a mutable composite type that contains the variable you wish to modify.

6 Likes

Thank you very much!
Could you be a little bit more specific how to do the Ref trick here?

Sorry for another stupid question, but why I can simply do

A = [1.0 1.0 ; 2.0 2.0]

outside a function.
However if A is an argument in an function, I have to do

A .= xxxxxx

The argument is like named boxes?

Thanks!

In your first example your are saying:

Assign the matrix in the right to the name "A".

In the second one you are saying:

Make every element in A equal to the corresponding element in the right. The “every element” part is what the dot is indicating (see “broadcasting”).

The first one does work inside the function, it is only that you are creating a local variable with the name A, you are not modifying the contents of the container that you received. When you exit the function your local variable is forgotten for ever.

var = Ref{Float64}(0.0) # Store it into a `Ref` which it's mutable but has a constant type

function f(var)
    var[] = 2.0
    return nothing
end

f(var)

var[] #prints 2.0
2 Likes

Thank you.
Yes, I can do that, but it seems then every time I have to use

 var[ ]  

to get its value, the

 [ ]

is slightly a little bit annoying.

It seems in Fortran such a thing is very easy but in Julia it seems a little bit tricky. :sweat_smile:

However I like the fact that Julia has a ! symbol to annotate a function is mutating its arguments.

Yes, using the Ref trick is not idiomatic (maybe someone differs?). In a typical julia program you would not use a ref because you don’t modify scalars (that was what I was trying to imply by calling it “trick”).

Migrating from a programming language to another is tricky, but I think you will make it easier for yourself if you try and think in julia. It is natural that, julia being a different language you will need to design things differently, so you may be putting obstacles in your on path by trying to write fortran with julia syntax.

5 Likes

I think the idiomatic style is as follows:

  • A mutable A is modified by a function as f!(A).
  • An immutable x is modified by a function usually as x = f(x).

Example:

function f!(A)
    A .= 1:length(A)
end

A = zeros(4)
f!(A)
@show A;
A = [1.0, 2.0, 3.0, 4.0]
f(x) = x + 1

x = 99
x = f(x)
@show x;
x = 100
4 Likes

In Julia the idiomatic way to do this is to take in arguments and return results (and maybe sometimes mutate one array input if appropriate).

4 Likes

Because it is very much against idiomatic Julia style to provide containers for scalar results as arguments.

It is very likely that you would be much better off if you didn’t try to use Julia as a kind of Fortran, because it isn’t.

8 Likes

It’s great that you want to ask so many questions. I notice that several other people have suggested that you read the manual. Most of the questions you have asked are covered quite well in that document. I really do strongly recommend that you read it. It will save you a lot of time.

You might be too young to have encountered the comment “if you want Fortran, you know where to get it” :wink:

3 Likes

Think this way: most times you define a function independently of a particular use case it will have. Thus, it is natural that a function receives a value and returns another values, without mutating the input, particularly for scalars. Thus the syntax:

function f(x)
   return x + 1
end

Now, if you want to use that function to “mutate” a value", just assign the new value to the output of that function, as others mentioned:

x = f(x)

I would say that having functions that mutate values is something that is a patch for the fact that creating large arrays is too expensive such that we cannot just use always non-mutating functions. In an ideal world, every function should be pure.

2 Likes

Something that (as far as I can tell) hasn’t been mentioned yet: you can return more than one thing from a function, which is why “modifying a scalar argument” isn’t something that’s generally done in julia. There’s no need for output arguments like in other languages such as C or Fortran (though of course sometimes passing in an already allocated array is better than allocating it in the function itself!). Consider:

julia> function updateAQT(A, Q, T)         
           A_new = A*5.0                   
           Q_new = Q / 2                   
           T_new = T^2                     
           return A_new, Q_new, T_new      
       end                                 
updateAQT (generic function with 1 method)
                                           
julia> a1, q1, t1 = updateAQT(1,1,1)
(5.0, 0.5, 1)                       
                                    
julia> a1                           
5.0                                 
                                    
julia> q1                           
0.5                                 
                                    
julia> t1                           
1                                   

Maybe something like that is what you’re looking for? Internally, this return A, B, C just constructs a tuple (so it’s the same as writing return (A, B, C)) which is returned instead. Outside of the function that tuple is destructured, by iterating over its elements. You can do the same thing with arrays, though it won’t be as efficient because of a couple of reasons.

I should not though that if you pass in variables instead of literals, you cannot directly access the memory location those variables point to inside of the called function. I.e. in updateAQT, I don’t have access to anything not defined inside of updateAQT (except for global variables, which I think have been discussed at length already).

5 Likes