Why I can't set what parameters are mutable explicitly?

I still don’t understand why parameters can be silently changed in function calls and why there is not a const or mutable like keyword to warn us if this is happening?

Running this example will silently change the content of the vector w/o knowing that.

a = [1, 2]
f(v) = v[2] = 3
f(a)

I found that ‘!’ after the name of a mutable function useless for general usage with multiple parameters.

a function like:

g!(a, b, c) 

is changing what?!?

Of course I don’t even touch the fact that ‘!’ is at this momment just some convention and not enforced by the compiler.

[Later edit:] Instead g!(a, b, c) should be something like: g(a!, b, c!) that tells that a and c are mutable. ‘!’ can be anything post or prefix.

1 Like

In your example, you are not changing “the array”, but the content of the array, commonly called “mutating”. Nothing is silently changing anything in your example. You explicitly wrote “set the second index of this array to 3”, and that’s what the code is doing. This is the case in most programming languages.

Putting ! is simply a convention, and has no effects on the code.

There is no way to prevent a function from mutating an array if that’s how it was written. What you can do in this case, is to pass a copy of your array: f(copy(a)).

4 Likes

I would dare to say that I’m changing it! Just compare it with a copy of that array done before the function call.

A similar C++ example:

void f(const vector<double> &r) { r[0] = 2.0; }

This example doesn’t even compile and errors out with: “error C3892: ‘r’: you cannot assign to a variable that is const”

I personaly, don’t care or see any important use of what you said, I care a lot the content of the array to be imuable when I make a function call that suposidly should be constant.

The trend in Julia is to make data structures entitely immutable after construction. In Julia 1.8, const fields were introduced to mutable structures.

julia> using StaticArrays

julia> a = SA[1,2]
2-element SVector{2, Int64} with indices SOneTo(2):
 1
 2

julia> f(v) = v[2] = 3
f (generic function with 1 method)

julia> f(a)
ERROR: setindex!(::SVector{2, Int64}, value, ::Int) is not defined.
Hint: Use `MArray` or `SizedArray` to create a mutable static array
Stacktrace:
...

const arguments would require some level of static analysis not easily available to Julia via its JIT compilation model.

4 Likes

Thank you but I don’t want a StaticArray I want a normal array that I’m changing in my code and is very expenssive to make a copy or conversion of it.

Try a ReadOnlyArray then:

julia> using ReadOnlyArrays

julia> A = rand(1024, 1024);

julia> ROA = ReadOnlyArray(A);

julia> f(v) = v[2] = 3
f (generic function with 1 method)

julia> f(A)
3

julia> f(ROA)
ERROR: setindex! not defined for ReadOnlyArray{Float64, 2, Matrix{Float64}}
Stacktrace:
 [1] error(::String, ::Type)
   @ Base ./error.jl:42
 [2] error_if_canonical_setindex(#unused#::IndexLinear, A::ReadOnlyArray{Float64, 2, Matrix{Float64}}, #unused#::Int64)
   @ Base ./abstractarray.jl:1323
 [3] setindex!
   @ ./abstractarray.jl:1314 [inlined]
 [4] f(v::ReadOnlyArray{Float64, 2, Matrix{Float64}})
   @ Main ./REPL[12]:1
 [5] top-level scope
   @ REPL[14]:1

julia> sizeof(ROA)
8

julia> ROA[2] # ROA is a no-copy view of A
3.0
10 Likes

Julia currently doesn’t support a way to prevent mutable objects from being mutated by functions. It simply isn’t as safe as some compiled languages such as C++, Fortran, or Rust. However, it has a lot of other great features like composability, dynamicism, speed, and reproducible environments. I personally agree that adding a feature like that to the language would be a good idea, but I have no idea how difficult it would be to implement. It most likely would have to wait for Julia 2.0 to be incorporated. If you feel strongly about it, then you could open an issue.

10 Likes

Some other references:

3 Likes

Great suggestion. Shouldn’t it be possible then to write a small macro @immutable that would be used as in the following?

function fun(a::AbstractVector, b::Real, c::AbstractVector)
    @immutable a c
    # From here on in the function, a and c are immutable
end

where the code generated by the macro would do something like the following:

a_orig = a  # Or preferably gensym a random name
a = ReadOnlyArray(a_orig)
# and the same for c

Your thoughts?

Note: I didn’t supply actual code for the macro since my macro-foo is pretty weak. I could work out the correct code eventually, but hoping for you or some other wizard to whip it up in a few seconds.

1 Like

I don’t need a read only array. I’m in the middle of changing the array and is expenssive to make copies.

Note that as @mkitti showed above, ReadOnlyArrays does not make a copy of the original array. It provides a so-called “wrapper” that is cheap and fast.

1 Like

I wrote over 4000 lines of Julia (mostly Cut and Paste) and easy over 1.000.000 of C++. I can say that Julia is like templates în C++. Is relatively strong typed even if we don’t like to say so. My problem is that I can’t understand when and what parameters are changed or not in a function call because parameters are not decorated to make this explicit.

You can’t decorate arguments to prevent mutation of standard arrays like in C++. If you absolutely require this feature, julia won’t work for you.

This is why there is the convention to add a bang ! to function names when mutation occurs. It’s also convention that the first argument is the one mutated.

You could always have documentation, and/or write a comment at the top of the function to make it explicit. See, for example, the push! docstring:

help?> push!
search: push! pushfirst! pushdisplay

  push!(collection, items...) -> collection

  Insert one or more items in collection. If collection is an ordered
  container, the items are inserted at the end (in the given order).
4 Likes

Ok so, again, the question is: A function like:

g!(a, b, c) 

is changing what parameters out of the three?!?

I understand this in other languages easy, for example looking at:
void g(int a, int& b, int& c)
I understand that the 2nd and 3rd parameter are changed inside the function, How do I understand this in Julia?

1 Like

If you wrote the g! function yourself, then you know which arguments you are mutating, if you didn’t write g!, you will need to look at the documentation or the code.

2 Likes

Note that in Julia, you cannot mutate Int. All arguments are passed by value. If you wanted to mutate a single number, you would need to wrap it in a container, like a Ref. The equilvalent signature in Julia is thus:

g(a::Int, b::Ref{Int}, c::Ref{Int})

that would give you exactly the same hint as to whether the arguments are changed as in C.

3 Likes

I didn’t wrote it I just want to use it or I want to use it 2 years after I wrote it and I forgot.

My C++ answer was with Ints is the same with containers and of course still doesn’t answer the main question:

What params are mutable looking at this function definition in Julia: g!(a, b, c)?

This has been answered a few times now: You cannot tell, it is not possible. It could be all, none, or any other subset.

8 Likes

It is possible to decorate arguments.

function f!(a!, b)
    a! .= 0
end

The compiler cannot take advantage of it, but the user can.

16 Likes