Thanks for the detailed reply, @StefanKarpinski.
My proposal has admittedly shortcomings of different sort, but I felt a need for such a discussion when Julia is still in its infancy. So, I hope more critique/support/improvements will appear.
I understand your explanation of dynamic and static types in C++, but here I am mostly interested in the clarity of syntax – not optimisations which would aid the compiler (as @foobar_lv2 might prefer).
Computational code is written and read by humans (not just machines), so clarity, explicity, and being less error-prone should be a goal for the design.
Let’s make an example; consider the following C/C++ snippet:
int foo(const int n, const* const int x, double y)
{
//... do stuff ...
return 0;
}
In this function definition, using const
modifiers is not just a means for producing optimised machine code; it is an explicit “promise” from the programmer (a human!); in words, it says:
The programmer promises that she will not reassign
n
orx
, and will respect the immutability ofx
in this part of the code.
Such a syntax will doubtlessly facilitate reasoning about the code for a second programmer who wants to read the code for understanding or debugging it.
Another example from FORTRAN, explicit declaration of pure functions:
pure function square(x)
real, intent(in) :: x
real :: square
square = x * x
end function
In this snippet, it is easy for a compiler to determine the purity of a function, but it adds explicit “promises” from the programmer, which can be verified thoroughly by a compiler.
Such explicitness, imo, reduces a lot of human mistakes in code, while promoting better coding styles (esp., being easy to read and reason about) – hence, I disagree with StefanKarpinski’s statement that “local immutability doesn’t really buy you much”.
I do not see why we shouldn’t have such explicitness in Julia which is meant mainly for modern computational tasks. Julia has already made a decision to make user-defined types immutable
by default, and to introduce an explicit keyword (mutable
) when mutability is required. As StefanKarpinski mentioned, Julia has also Immutable Arrays
– honestly, I do not understand the conceptual difference between a Tuple
and an Immutable Array
, and how Immutable Array
fits into the type hierarchy (regarding StefanKarpinski’s statement, “a value can be mutated or not is a property of its type and that type cannot change…”).
I think, one can systematically generalize the aforementioned schemes to all types, for instance, by a “mutation” of my proposal:
- Aggregate types – meaning the types which you can add or remove elements – like
Arrays
,Vector
s,Matrices
,Dict
s,Set
s, Graphs, Lists, etc., are mutable by default, but one can “wrap [them] in an immutable wrapper” (as StefanKarpinski mentioned) to use them as immutables in some restricted scope (eg., in the body of a function); e.g.,
x = Array{Int64}(10) # constant binding to a mutable Array
xm = Immutable(x) # constant binding to an immutable view of x
- Non-aggregate types or user-defined types will be immutable by default (as the current standard for
struct
).
I still believe that one should keep a clear distinction between equality and assignment to markup modifications to the state:
As in part (1) of my proposal, use var
and <-
(or :=
) when dealing with (re-)assignments or modifying mutables, and reserve the equality operator (=
) for a constant binding.
The function signature should be also explicit in this regard, as I proposed in my original post.