The "It" in my second sentence was supposed to refer to "const in C", but I deleted an interposing sentence as I was revising. But the argument applies to both.
In C, violating const is only a compile-time warning. And it is trivial to cast away const. One website dedicated to reducing coding errors, on the page saying not to cast away const, ( https://www.securecoding.cert.org/confluence/display/seccode/EXP05-C.+Do+not+cast+away+a+const+qualification#), lists several exceptions where it is sometimes necessary. The problem, as you mentioned, is aliasing. This effectively prevents the callee from actually making any meaningful (or useful) assumptions about a const argument. In fact, since the Julia compiler (like most compilers) already knows whether or not a function might modify an object, it wouldn't even enable extra optimizations. Therefore adding it as an argument flag would simply be a compile-time check between what the compiler knows and what the user specified (the same as C). In general, types (such as your proposed ReadOnlyArray wrapper and no-method errors) can be verified as compile-time constructs. However, most of the benefits you claim could realized only if the caller is responsible for the annotation. The callee might have no problem marking the wrong matmul matrix readonly without noticing, but the caller would have to get it correct. Similarly, if the compiler knows that the callee is willing to make a guarantee of const-ness it might be able to generate more efficient code. Type-based dispatch can be great for this (since it allows the compiler to make a compile-time decision whether the method applies, or can use this information for optimization). On Wed, Aug 20, 2014 at 9:53 PM, <[email protected]> wrote: > Jameson, > > My proposal for inargs is here: > https://groups.google.com/forum/#!searchin/julia-users/inargs/julia-users/FK_29Dj4eDo/A_drp4-i8DcJ > > It is not compile-time; it is run-time. And it is not a warning; it is an > error to violate the constraint. And I'm not sure what you mean by "weak": > there is no way to bypass it except via aliasing (i.e., passing the same > actual data item as two separate arguments to a routine, once as a readonly > and once not). It does have the drawback that it doesn't work on recursive > data structures; it will throw an error if you declare an array of arrays > as a readonly argument. > > In a subsequent posting in that thread, I listed three possible benefits > for readonly args. Two of them (preventing blunders such as matmul > arguments in the wrong order; enabling simpler reasoning about code) would > be realized with enforcement of readonly either by the caller or the > function. The third one (enabling automatic program transformations), > however, is possible only if the function rather than the caller imposes > the readonly requirement. > > -- Steve Vavasis > > > > On Tuesday, August 5, 2014 5:38:17 PM UTC-4, [email protected] wrote: > >> Dear Julia users, >> >> It seems to me that Julia's distinction between a 'type' and an >> 'immutable' conflates two independent properties; the consequence of this >> conflation is a needless loss of performance. In more detail, the >> differences between a 'type' struct and 'immutable' struct in Julia are: >> >> 1. Assignment of 'type' struct copies a pointer; assignment of an >> 'immutable' struct copies the data. >> >> 2. An array of type structs is an array of pointers, while an array of >> immutables is an array of data. >> >> 3. Type structs are refcounted, whereas immutables are not. (This is not >> documented; it is my conjecture.) >> >> 4. Fields in type structs can be modified, but fields in immutables >> cannot. >> >> Clearly #1-#3 are related concepts. As far as I can see, #4 is >> completely independent from #1-#3, and there is no obvious reason why it is >> forbidden to modify fields in immutables. There is no analogous >> restriction in C/C++. >> >> This conflation causes a performance hit. Consider: >> >> type floatbool >> a::Float64 >> b:Bool >> end >> >> If t is of type Array{floatbool,1} and I want to update the flag b in >> t[10] to 'true', I say 't[10].b=true' (call this 'fast'update). But if >> instead of 'type floatbool' I had said 'immutable floatbool', then to set >> flag b in t[10] I need the more complex code t[10] = >> floatbool(t[10].a,true) (call this 'slow' update). >> >> To document the performance hit, I wrote five functions below. The first >> three use 'type' and either no update, fast update, or slow update; the >> last two use 'immutable' and either no update or slow update. You can see >> a HUGE hit on performance between slow and fast update for `type'; for >> immutable there would presumably also be a difference, although apparently >> smaller. (Obviously, I can't test fast update for immutable; this is the >> point of my message!) >> >> So why does Julia impose this apparently needless restriction on >> immutable? >> >> -- Steve Vavasis >> >> >> julia> @time testimmut.type_upd_none() >> @time testimmut.type_upd_none() >> elapsed time: 0.141462422 seconds (48445152 bytes allocated) >> >> julia> @time testimmut.type_upd_fast() >> @time testimmut.type_upd_fast() >> elapsed time: 0.618769232 seconds (48247072 bytes allocated) >> >> julia> @time testimmut.type_upd_slow() >> @time testimmut.type_upd_slow() >> elapsed time: 4.511306586 seconds (4048268640 bytes allocated) >> >> julia> @time testimmut.immut_upd_none() >> @time testimmut.immut_upd_none() >> elapsed time: 0.04480173 seconds (32229468 bytes allocated) >> >> julia> @time testimmut.immut_upd_slow() >> @time testimmut.immut_upd_slow() >> elapsed time: 0.351634871 seconds (32000096 bytes allocated) >> >> module testimmut >> >> type xytype >> x::Int >> y::Float64 >> z::Float64 >> summed::Bool >> end >> >> immutable xyimmut >> x::Int >> y::Float64 >> z::Float64 >> summed::Bool >> end >> >> myfun(x) = x * (x + 1) * (x + 2) >> >> function type_upd_none() >> n = 1000000 >> a = Array(xytype, n) >> for i = 1 : n >> a[i] = xytype(div(i,2), 0.0, 0.0, false) >> end >> numtri = 100 >> for tri = 1 : numtri >> sum = 0 >> for i = 1 : n >> @inbounds x = a[i].x >> sum += myfun(x) >> end >> end >> end >> >> >> function type_upd_fast() >> n = 1000000 >> a = Array(xytype, n) >> for i = 1 : n >> a[i] = xytype(div(i,2), 0.0, 0.0, false) >> end >> numtri = 100 >> for tri = 1 : numtri >> sum = 0 >> for i = 1 : n >> @inbounds x = a[i].x >> sum += myfun(x) >> @inbounds a[i].summed = true >> end >> end >> end >> >> function type_upd_slow() >> n = 1000000 >> a = Array(xytype, n) >> for i = 1 : n >> a[i] = xytype(div(i,2), 0.0, 0.0, false) >> end >> numtri = 100 >> for tri = 1 : numtri >> sum = 0 >> for i = 1 : n >> @inbounds x = a[i].x >> sum += myfun(x) >> @inbounds a[i] = xytype(a[i].x, a[i].y, a[i].z, true) >> end >> end >> end >> >> >> function immut_upd_none() >> n = 1000000 >> a = Array(xyimmut, n) >> for i = 1 : n >> a[i] = xyimmut(div(i,2), 0.0, 0.0, false) >> end >> numtri = 100 >> for tri = 1 : numtri >> sum = 0 >> for i = 1 : n >> @inbounds x = a[i].x >> sum += myfun(x) >> end >> end >> end >> >> function immut_upd_slow() >> n = 1000000 >> a = Array(xyimmut, n) >> for i = 1 : n >> a[i] = xyimmut(div(i,2), 0.0, 0.0, false) >> end >> numtri = 100 >> for tri = 1 : numtri >> sum = 0 >> for i = 1 : n >> @inbounds x = a[i].x >> sum += myfun(x) >> @inbounds a[i] = xyimmut(a[i].x, a[i].y, a[i].z, true) >> end >> end >> end >> >> end >> >> >> >> >
