www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - "headconst" dynamic arrays?

reply bearophile <bearophileHUGS lycos.com> writes:
(Probably this topic was already discussed in past, if this is the case them I
am sorry).

I like to use both static and dynamic languages. In a statically typed language
as D if I have to pay for type annotations and related troubles, I want them to
give me back as much as possible.

Putting "in" (or const/immutable) before a function argument makes it constant,
this is useful for code documentation, to avoid changing the argument by
mistake inside the function, and for stronger purity.

In this function foo() the argument 'arr' can't be const because the function
has to modify its contents somehow. But often even if I have to modify the
given array contents, I don't have to change its length or rebind its ptr
(assign it to another array). (In this program 'a' in the main() will not
change its length nor the memory it refers too, but the bug is present still,
because the writeln gives a wrong output) (sometimes the argument is a string,
it's the same thing).


import std.stdio;
void foo(int[] arr) {
    // some code here...
    arr.length += 1; // a bug
    // some code here...
    foreach (ref x; arr)
        x++;
    writeln(arr);
}
void main() {
    int[] a = [1, 2, 3];
    foo(a);
}


So in my code I sometimes like "headconst dynamic array", that is an array that
allows changes in its contents but not its length. Currently in D there is no
way to specify this kind of array (there is a way to specify tailconst arrays,
like const(int)[], but this is the opposite of what I'm looking for).

A possible curious syntax :-)

void foo(int const([]) arr) {
    // some code here...
    arr.length += 1; // problem caught!
    arr = new int[5]; // not allowed
    // some code here...
    foreach (ref x; arr)
        x++;
    writeln(arr);
}


The space between int and const is necessary, you can't write it as:
void foo(intconst([]) arr) {

One alternative syntax (only one syntax is accepted, of course):
void foo(int(const[]) arr) {


That curious first syntax seems to work for nD arrays too:

Both dimensions are headconst:
void bar(int const([][]) mat2d) {
Or:
void bar(int (const[][]) mat2d) {

Only 2 dimensions of 3 are headconst:
void spam(int const([][])[] mat3d) {
Or:
void spam(int (const[][])[] mat3d) {


Is this too much complexity for a corner case? Most dynamic arrays are passed
with "in/const". Is this little problem better solved with user/library code?

Bye,
bearophile
Aug 31 2011
next sibling parent reply travert phare.normalesup.org (Christophe) writes:
headconst seems a very useful thing.

 void foo(int(const[]) arr)

-- Christophe Travert
Aug 31 2011
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Christophe Travert:

 headconst seems a very useful thing.

In C you are allowed to write: #include <stdio.h> void foo(int * const arr, const int arr_len) { // some code here... arr_len += 1; // a bug int arr2[] = {10, 20, 30, 40}; arr = arr2; // some code here... for (int i = 0; i < arr_len; i++) arr[i]++; for (int i = 0; i < arr_len; i++) printf("%d ", arr[i]); putchar('\n'); } int main() { int a[] = {1, 2, 3}; foo(a, 3); return 0; } Here GCC 4.6.1 gives: test.c: In function 'foo': test.c:4:5: error: assignment of read-only parameter 'arr_len' test.c:6:5: error: assignment of read-only parameter 'arr' Dynamic arrays that don't change their length and don't allow rebinding, but allow changes in their contents, are useful. I use arrays every day in D and this desire arises now and then. It's hard to deny this. But the point are: how much useful it is in D? Is the added language complexity worth it? Is it possible to design semantically sound conversions between the various kind of arrays? Is it possible/better a library solution for it?
 void foo(int(const[]) arr)


OK. The first syntax doesn't have this problem. Bye, bearophile
Aug 31 2011
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, August 31, 2011 05:42:07 bearophile wrote: 
 Dynamic arrays that don't change their length and don't allow rebinding, but
 allow changes in their contents, are useful. I use arrays every day in D
 and this desire arises now and then. It's hard to deny this.

Um. Just don't change their length? Sure, it would be nice to be able to have this feature, but it's hardly the only issue in D where you can't have a particular combination of const and mutable due to how D's const works, and really, all you have to do is not alter the length of the array in the function that it's passed to. So, yes, it would be nice, but I don't think that it's a big deal. Of far greater importance is being able to make the elements const, and we can do that quite easily already. - Jonathan M Davis
Aug 31 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Jonathan M Davis:

 it's hardly the only issue in D where you can't have a 
 particular combination of const and mutable due to how D's const works,

Right. The difference between this idea and those particular combinations is that I think this situation has a solution and a syntax. Bye, bearophile
Aug 31 2011
parent reply dsimcha <dsimcha yahoo.com> writes:
Have we considered the obvious library head-const solution?

struct HeadConst(T) {
    T __payload;

    T __get() {
        return __payload;
    }

    alias __get this;
}

HeadConst!T headConst(T)(T stuff) { return typeof(return)(stuff); }

Usage:

auto headConstArray = headConst([1, 2, 3]);

The only problem I see is that you'd get unnecessary copying of structs with
postblits.  IMHO, though, the language spec should be relaxed to allow copying
to
be elided due to function inlining.
Aug 31 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
dsimcha:

 Have we considered the obvious library head-const solution?

Yeah. This code seems to work. The main disadvantage is that you have to convert the dynamic array to Hconst before passing it to the function, while the syntax I have suggested is transparent for the caller. Another disadvantage is that this doesn't support nD arrays well (only the first dimension is head const). import std.traits, std.stdio, std.conv; struct Hconst(T) { private T[] data__; alias data__ this; property size_t length() { return data__.length; } property T* ptr() { return data__.ptr; } string toString() { return text(data__); } // ? } Hconst!(typeof(TA[0])) hconst(TA)(TA array) if (isDynamicArray!TA) { return typeof(return)(array); } // Overload: hconst(hconst(new int[10])) === hconst(new int[10]) void main() { auto a = new int[10]; Hconst!int ha = hconst(a); ha.ptr[0] = 1; writeln(ha); ha[1] = 2; writeln(ha); writeln(ha.length); // ha.length += 1; // error, OK } Bye, bearophile
Sep 01 2011
parent bearophile <bearophileHUGS lycos.com> writes:
Another disadvantage I didn't consider is that a Hconst!T converts silently to
T[], so if you give a function, the function is free to ignore the const nature
of ptr/length:


struct Foo(T) {
    private T[] data__;
    alias data__ this;
}

void bar1(Foo!int ha) {}
void bar2(int[] a) {}

void main() {
    Foo!int fa = Foo!int(new int[10]);
    bar1(fa); // OK
    bar2(fa); // OK
    //bar1(new int[10]); // NO
    int[] a = Foo!int(new int[10]); // OK
}

I didn't know this about alias this. I presume it is working as designed.

Bye,
bearophile
Sep 01 2011
prev sibling parent "Martin Nowak" <dawg dawgfoto.de> writes:
On Thu, 01 Sep 2011 13:48:25 +0200, bearophile <bearophileHUGS lycos.com>  
wrote:

 Another disadvantage I didn't consider is that a Hconst!T converts  
 silently to T[], so if you give a function, the function is free to  
 ignore the const nature of ptr/length:


 struct Foo(T) {
     private T[] data__;
     alias data__ this;
 }

 void bar1(Foo!int ha) {}
 void bar2(int[] a) {}

 void main() {
     Foo!int fa = Foo!int(new int[10]);
     bar1(fa); // OK
     bar2(fa); // OK
     //bar1(new int[10]); // NO
     int[] a = Foo!int(new int[10]); // OK
 }

 I didn't know this about alias this. I presume it is working as designed.

 Bye,
 bearophile

Somewhat surprising from time to time and one of the downsides of alias this. The proposed HeadConst was aliasing to an rvalue, so even with conversion you can't change the length. martin
Sep 01 2011
prev sibling parent reply Kagamin <spam here.lot> writes:
bearophile Wrote:

 Is this too much complexity for a corner case? Most dynamic arrays are passed
with "in/const". Is this little problem better solved with user/library code?

Looks like a coding style. Can be done with a source analyzer or a compiler switch.
Aug 31 2011
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Kagamin:

 Looks like a coding style. Can be done with a source analyzer or a compiler
switch.

I don't understand. What do you mean? Bye, bearophile
Aug 31 2011
parent reply Kagamin <spam here.lot> writes:
bearophile Wrote:

 I don't understand. What do you mean?

It's not needed to extend type system. Compiler can check you don't set arguments indiscriminately. I believe some source analyzer already does it.
Aug 31 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Kagamin:

 It's not needed to extend type system. Compiler can check you don't set
arguments indiscriminately. I believe some source analyzer already does it.

Some functions need to do everything on a given array, while in other functions I just want to change the array contents. How can the source analyzer tell them apart and show some warnings only on the second kind of functions, without specific annotations? Bye, bearophile
Aug 31 2011
parent reply Kagamin <spam here.lot> writes:
bearophile Wrote:

 Some functions need to do everything on a given array, while in other
functions I just want to change the array contents. How can the source analyzer
tell them apart and show some warnings only on the second kind of functions,
without specific annotations?

It will check them indiscriminately. There's little need to overwrite input parameters. Input parameters are by nature likely to be read, overwriting them usually means you lose them, and losing parameters that are likely to be read is a bad practice, so it's a good idea to check for it.
Aug 31 2011
parent bearophile <bearophileHUGS lycos.com> writes:
Kagamin:

 It will check them indiscriminately. There's little need to overwrite input
parameters.

If I don't need to overwrite an input array I usually tag it with "in" or "const", so your idea doesn't help me. Bye, bearophile
Aug 31 2011
prev sibling next sibling parent reply "Marco Leise" <Marco.Leise gmx.de> writes:
Am 31.08.2011, 20:27 Uhr, schrieb Kagamin <spam here.lot>:

 bearophile Wrote:

 Some functions need to do everything on a given array, while in other  
 functions I just want to change the array contents. How can the source  
 analyzer tell them apart and show some warnings only on the second kind  
 of functions, without specific annotations?

It will check them indiscriminately. There's little need to overwrite input parameters. Input parameters are by nature likely to be read, overwriting them usually means you lose them, and losing parameters that are likely to be read is a bad practice, so it's a good idea to check for it.

I had some of these cases in my JavaScript code where I process the parameter before I use it. But I'm now listening to Eclipse's advice and give them proper variables. So zoom becomes effectiveZoom and so on. But I guess it is a matter of preference. It is like Delphi's "Result" variable in functions that you can write to several times until you have your final output. Likewise people sometimes modify the input until they've got what they want. Clamping numbers, prefixing strings or replacing default/null values are use cases.
Aug 31 2011
parent bearophile <bearophileHUGS lycos.com> writes:
Marco Leise:

 Likewise people sometimes modify the input until they've got what  
 they want. Clamping numbers, prefixing strings or replacing default/null  
 values are use cases.

Think about an in-place sort routine. I give an array to it, and its items get shuffled, but nowhere inside the sort routine I want the array length to change. I'd like the array length to be const, but the array items to be mutable. In Phobos there are other examples of in-pace array functions. A system language offers such freedom too. Bye, bearophile
Aug 31 2011
prev sibling parent "Marco Leise" <Marco.Leise gmx.de> writes:
Am 31.08.2011, 21:44 Uhr, schrieb bearophile <bearophileHUGS lycos.com>:

 Marco Leise:

 Likewise people sometimes modify the input until they've got what
 they want. Clamping numbers, prefixing strings or replacing default/null
 values are use cases.

Think about an in-place sort routine. I give an array to it, and its items get shuffled, but nowhere inside the sort routine I want the array length to change. I'd like the array length to be const, but the array items to be mutable. In Phobos there are other examples of in-pace array functions. A system language offers such freedom too. Bye, bearophile

Yeah I was specifically talking about a warning when you overwrite the parameter, not modify it's properties or content. It's probably a good idea to keep parameters 'head-const' by convention in every case unless they are out parameters. You want the array to be partially modifiable or - regarding length and ptr - head-const so routines meant to work on the content only can be forced to do so. But then the next thing I would request is the same for other ranges. And ranges can be classes, too. I don't think the concept can work out if you want to process all ranges the same way in sorting algorithms and the like.
Aug 31 2011