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.
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)).
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.
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.
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.
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.
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).
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?
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.
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.