February 18, 2012
On 18/02/2012 22:57, Andrej Mitrovic wrote:
> Are you familiar with cases where you want regular arrays to return
> Type.init when you go out of bounds?

The front page says D isn't meant to be an orthogonal language :P

If you want orthogonality, then associative arrays will have to work something like this:

int[string] stuff;

stuff.addKey("a");
stuff.addKey("b");
stuff.addKey("d");
stuff.addKey("e");

stuff["a"]=0;
stuff["b"]=1;
stuff["c"]=2; //error
writefln("%s",stuff["d"]);
writefln("%s",stuff["e"]);
writefln("%s",stuff["f"]); //error

Do you want to do that?
February 18, 2012
To be clear, I'm not too bothered how associative arrays work. My proposal was merely a means by which the following currently working code:

stuff[previouslyNonexistentKey]++;

could continue to work without relying on a current implementation quirk-possibly-bug.

If you want to change it not to work and make people's existing code crash, you can :)

On 18/02/2012 23:08, Ben Davis wrote:
> On 18/02/2012 22:57, Andrej Mitrovic wrote:
>> Are you familiar with cases where you want regular arrays to return
>> Type.init when you go out of bounds?
>
> The front page says D isn't meant to be an orthogonal language :P
>
> If you want orthogonality, then associative arrays will have to work
> something like this:
>
> int[string] stuff;
>
> stuff.addKey("a");
> stuff.addKey("b");
> stuff.addKey("d");
> stuff.addKey("e");
>
> stuff["a"]=0;
> stuff["b"]=1;
> stuff["c"]=2; //error
> writefln("%s",stuff["d"]);
> writefln("%s",stuff["e"]);
> writefln("%s",stuff["f"]); //error
>
> Do you want to do that?

February 18, 2012
Well it's probably too late to change this behavior. Both the sample on the hash page and TDPL itself shows the usage of that trick.

Btw, if you really want Type.init if the key doesn't exist you can use
the get method:
Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();
February 18, 2012
On 2/19/12, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote:
> Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();

I've tried making a wrapper type that does this behind the scenes but it won't work:

struct Hash(Key, Val)
{
    Val[Key] aa;

    Val opIndex(Key key)
    {
        return aa.get(key, Val.init);
    }

    alias aa this;
}

class Chunk { }
alias Hash!(string, Chunk) ChunkHash;

void main()
{
    ChunkHash chunks;
    Chunk[] tempVar = chunks["CCCC"] ~ new Chunk();
}

test.d(20): Error: incompatible types for ((chunks.opIndex("CCCC")) ~
(new Chunk)): 'test.Chunk' and 'test.Chunk'

Very odd..
February 19, 2012
On 18/02/2012 23:33, Andrej Mitrovic wrote:
> Well it's probably too late to change this behavior. Both the sample
> on the hash page and TDPL itself shows the usage of that trick.

That's fine - but let's document it :)

A few things seem to be missing:

- You get a RangeError for reading a nonexistent key;

- However, you can safely write a[k]=a[k]+1 or a[k]++, because if a[k] doesn't exist, then a[k]=b sets a[k] to the default value first before evaluating b. (This is a special case for assoc array assignments, not for other assignments.)

- .init property for assoc arrays returns null.

- For dynamic arrays and assoc arrays, 'null' is an empty array, so you don't have to worry about null crashes like with objects.

Here are the tests I did to confirm the above:

writefln("%s",cast(int[string])null);
//prints "[]"

int[string] assoc=null;
writefln("%s",assoc.length);
//prints 0

writefln("%s",(cast(int[string])null).length);
//breaks the compiler for me :P but not an important use case

int[] dyn=null;
writefln("%s",dyn.length);
//prints 0

> Btw, if you really want Type.init if the key doesn't exist you can use
> the get method:
> Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();

Yes, good to know! I see it's in the docs too. Thanks :)
February 19, 2012
On 02/19/2012 12:40 AM, Andrej Mitrovic wrote:
> On 2/19/12, Andrej Mitrovic<andrej.mitrovich@gmail.com>  wrote:
>> Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();
>
> I've tried making a wrapper type that does this behind the scenes but
> it won't work:
>
> struct Hash(Key, Val)
> {
>      Val[Key] aa;
>
>      Val opIndex(Key key)
>      {
>          return aa.get(key, Val.init);
>      }
>
>      alias aa this;
> }
>
> class Chunk { }
> alias Hash!(string, Chunk) ChunkHash;
>
> void main()
> {
>      ChunkHash chunks;
>      Chunk[] tempVar = chunks["CCCC"] ~ new Chunk();
> }
>
> test.d(20): Error: incompatible types for ((chunks.opIndex("CCCC")) ~
> (new Chunk)): 'test.Chunk' and 'test.Chunk'
>
> Very odd..

The error is unrelated to your wrapper type.

static assert(!is(typeof(1~1)));

Concatenation only works if at least one of the types is an array.
February 19, 2012
"Ben Davis" <entheh@cantab.net> wrote in message news:jhotcm$13ag$1@digitalmars.com...
> I've seen some line-blurring between 'null' and 'empty' for dynamic arrays (non-associative). Specifically, I read that array.init returns null for both static and dynamic, but I think I also read that a dynamic array's default value is the empty array. I also observed that null~[1] == [1], and I wondered if actually 'null' becomes an empty array when cast to dynamic array and they're effectively the same thing.
>

null is not the same thing as an empty array, and I'm not aware of any
situations where null will implicitly turn into one.
null == { length == 0, ptr == null }
empty == { length == 0, ptr != null }
To make an empty array you (generally) need to allocate memory for it, and
having this happen implicitly would be a problem.

> If I'm right, then the same could be true for assoc arrays - that 'null' cast to an assoc array type becomes an empty assoc array. Which would explain the magic you're seeing.

This is not what's happening.

With the lvalue AA lookup, the call turns into this:
*_d_aaGet(&AA, keyinformation ...) = value;

Because it passes a pointer to the actual AA variable, if the AA doesn't exist it is created.  All of the rvalue AA methods behave the same for null and empty AAs.

Except for this magic initialization, AAs behave the same as classes - ie a reference type.


February 19, 2012
On 2/19/12, Timon Gehr <timon.gehr@gmx.ch> wrote:
> Concatenation only works if at least one of the types is an array.

Ah, good point. Still can be worked around:

struct Hash(Key, Val)
{
    struct WrapVal(Val)
    {
        Val val;

        auto opCat(Val rhs)
        {
            return [val] ~ rhs;
        }

        alias val this;
    }

    alias WrapVal!Val ValType;

    ValType[Key] aa;

    ValType opIndex(Key key)
    {
        return aa.get(key, ValType.init);
    }

    alias aa this;
}

class Chunk { }
alias Hash!(string, Chunk) ChunkHash;

void main()
{
    ChunkHash chunks;
    Chunk[] tempVar = chunks["CCCC"] ~ new Chunk();
}
February 19, 2012
On 19/02/2012 03:31, Daniel Murphy wrote:
> "Ben Davis"<entheh@cantab.net>  wrote in message
> news:jhotcm$13ag$1@digitalmars.com...
>> I've seen some line-blurring between 'null' and 'empty' for dynamic arrays
>> (non-associative). Specifically, I read that array.init returns null for
>> both static and dynamic, but I think I also read that a dynamic array's
>> default value is the empty array. I also observed that null~[1] == [1],
>> and I wondered if actually 'null' becomes an empty array when cast to
>> dynamic array and they're effectively the same thing.
>>
>
> null is not the same thing as an empty array, and I'm not aware of any
> situations where null will implicitly turn into one.
> null == { length == 0, ptr == null }
> empty == { length == 0, ptr != null }
> To make an empty array you (generally) need to allocate memory for it, and
> having this happen implicitly would be a problem.

So 'null' implicitly turns into { length == 0, ptr == null } when implicitly cast to the array type (and then it behaves like an empty array in many situations). That needs documenting :) Coming from any of C, C++ or Java, you would think of null (or NULL) as a pointer to 0, which will crash if you try to dereference it in any way - so the fact that (null array).length is valid and gives 0 is not obvious!

Thanks for explaining it to me in any case :)

>> If I'm right, then the same could be true for assoc arrays - that 'null'
>> cast to an assoc array type becomes an empty assoc array. Which would
>> explain the magic you're seeing.
>
> This is not what's happening.
>
> With the lvalue AA lookup, the call turns into this:
> *_d_aaGet(&AA, keyinformation ...) = value;
>
> Because it passes a pointer to the actual AA variable, if the AA doesn't
> exist it is created.  All of the rvalue AA methods behave the same for null
> and empty AAs.

... Cool!

> Except for this magic initialization, AAs behave the same as classes - ie a
> reference type.

That's not quite true, because 'length' is passed around by value alongside the reference, leading to semantics you could never reproduce with classes, unless I'm mistaken.
February 19, 2012
"Ben Davis" <entheh@cantab.net> wrote in message news:jhr0qf$24sj$1@digitalmars.com...
> On 19/02/2012 03:31, Daniel Murphy wrote:
>> Except for this magic initialization, AAs behave the same as classes - ie
>> a
>> reference type.
>
> That's not quite true, because 'length' is passed around by value alongside the reference, leading to semantics you could never reproduce with classes, unless I'm mistaken.

AAs, not Arrays.