Expressing Const in Formal Parameter Arguments to Functions

related to Make function paramaters immutable by default - #31 by Per .

Is there even/now a way to express in the formal parameters of a function that the function should not / does not modify contents? I could think both C-type type * const and const type * could be useful in catching bugs in the function itself, beside the fact that it could help compiler optimizations.

I know that the ‘!’ is a convention to designate functions that modify memory being pointed to, but it’s just convention (and there may be multiple arguments).

regards,

/iaw

No there isn’t. C const also doesn’t actually have that semantic significance…

There are talks about making variable constant (i.e. not allowing assignment but allow mutation) and that should be a pretty useful feature though not high priority (it doesn’t stop anyone from writing any code).

Requiring immutability of mutable object is a much harder problem and impossible to be done statically. Therefore, it’ll most likely be a good feature to Lint rather than the compiler/runtime itself (disclaimer I didn’t check if Lint already has such function).

2 Likes

In Julia 0.7, there’s the possibility to overload Base.setfield!, which would make it trivial to write a package that implements a wrapper type ReadOnly{T} that protects a mutable struct T from accidental modification by throwing an error on assignments like x.y = z.

As with C const, using it would require some effort. You’d have to go through all functions that might get called with a T and see if they also should accept ReadOnly{T}, making it annoying to work with third-party libraries that don’t use this annotation. (You’d either have to strip away the ReadOnly wrapper before each function call, or modify the third-party code.)

Also like C const, there’d be no way to prevent deliberate modification (e.g. using Core.setfield!).

2 Likes

Since there currently exist no compiler optimizations that would make use of const mutable, I see the main points in documentation and catching bugs. That is, for testing, not production.

For documentation, I really like the proposed coding style of attaching an exclamation mark to all mutated formal argument names. You can do this in your own code today.

For catching bugs, that would be part of the debugger (you care about memory writes, not julia mutations, and a “memconst” declaration would be equivalent to setting a page read-only in the debugger, and a NOP in production; this is the way you prevent deliberate modification in C).

2 Likes

it is mostly about catching bugs. in my real code (not the snippets for discourse), I have tons of asserts, now all preceded by @inbounds. when I know my program is bug-free, it runs faster. in many of my analysis programs, debug time >> run time.

there is a second aspect, though—readability of code years later or by others. If you see

f!(x::Vector, y::Vector, z::Vector)

you have a hint that something may change, but which one? obviously, this can be made far more complex.

f!(unmodified x::Vector, modified y::Vector, unmodified z::Vector)

(bad syntax, of course) could tell you that y is the one that may be changing. interestingly, even the modified could be useful, to avoid bugs where someone writes y= [2,3] instead of y.=[2,3].

debugging is one thing, and yes useful. expressing it in the code can be even more useful, esp if speed penalties (if any) can be turned off after it is complete. this is also why we keep source rather than jit code.

all for version 2.0 or 3.0…

/iaw

You can, today, define function f!(x!::Vector, y::Vector, z!::Vector). Base does not currently follow this convention, but if you like it we can propagate it by voting with the code we write.

2 Likes

I asked on the predecessor discussion group about having const input arguments to functions a long time ago (around 2014). Stefan Karpinski responded that adding new types like const Vector{T} would make the type hierarchy much more difficult to understand (e.g., how would such types interact with multiple dispatch). I found this argument convincing. Tim Holy pointed out that a user could implement this in his/her own code. For example, you could write something like

   struct ReadOnlyVector{T}
      a::Vector{T}
   end

and then define a getindex but not setindex for ReadOnlyVector. Functions could declare formal parameters like v::ReadOnlyVector{Int}, and callers could use a syntax like f(ReadOnlyVector(s)). I never implemented this, but maybe someone else has a package. As far as I know, there wouldn’t be any performance loss.

1 Like

if const were to become part of a type hierarchy for dispatch, then stefan is absolutely correct. lots of complications. HOWEVER, not everything needs to be. for example, functions do not dispatch differently based on return type. similarly, this could be a non-dispatch aspect of arguments.

at the beginning, I think this could start out with merely being syntactic sugar—no functionality, just an expressive “advisory” keyword. eventually, a lint or compiler could instrument this into errors or warnings. over more time, it could help the compiler optimize code.

then again, I had hoped to convince the julia folks to add endfor, endif, endfunction, enddo, etc. as aliases for ‘end’, without any new functionality, but they did not like it, either.

regards,

/iaw