Dan, from your comment it sounds like you aren’t appreciating exactly how ReadOnlyArrays
can be used for your purposes. Please consider the following example function, where I intend to mutate the first argument to be the reversed cumulative sum of the second argument. The second argument is annotated with @immutable
to ensure it is not accidentally mutated in this function:
function revcumsum!(a::Vector, b::AbstractVector)
@immutable b # Disallow mutations to second argument
length(a) == length(b) || throw(ArgumentError("arguments must have same length"))
a .= b
cumsum!(a, b)
reverse!(a)
return a
end
Now we exercise the function on an array of one million elements. Note that both arrays are mutable outside of the function:
julia> b = collect(1:1_000_000);
julia> a = similar(b);
julia> revcumsum!(a, b);
julia> b[1:5]
5-element Vector{Int64}:
1
2
3
4
5
julia> a[1:5] # contains the reversal of the cumulative sum of b
5-element Vector{Int64}:
500000500000
499999500000
499998500001
499997500003
499996500006
julia> b[1] # Still retains original value
1
julia> b[1] = 0 # I am mutating b
0
julia> b[1:5] # We see that the mutation occurred as requested
5-element Vector{Int64}:
0
2
3
4
5
The point is that within the function, and only within the function, b
becomes immutable. Also, we see that the function does not perform any memory allocation:
julia> using BenchMarkTools
julia> @btime revcumsum!($a, $b);
1.390 ms (0 allocations: 0 bytes)
And if we make a similar function that omits the @immutable
statement:
julia> function revcumsum2!(a::Vector, b::AbstractVector)
length(a) == length(b) || throw(ArgumentError("arguments must have same length"))
a .= b
cumsum!(a, b)
reverse!(a)
return a
end
revcumsum2! (generic function with 1 method)
julia> @btime revcumsum2!($a, $b);
1.408 ms (0 allocations: 0 bytes)
the execution time is essentially identical to the original function that includes the @immutable
statement. The execution time cost is negligible.
So to summarize, the point of @immutable
is to allow you to annotate and disallow mutation of mutable arrays (of any size) within functions that you define. That way the function caller does not have to do anything special. The presence of the @immutable
statement (typically and preferentially) on the second line of the function permits a quick inspection of the function to reveal which arguments could or could not be mutated.
That is the primary use case that I was thinking of. If I understand your wishes correctly, I believe that this goes at least partway (i.e. for functions that you yourself write) towards the goal you seek.