www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - AA revisited

reply David Medlock <noone nowhere.com> writes:
I have to say the AA changes are confusing to say the least.

With classes using  'lookup[key]'  would create an (key,value) *entry* 
but not an *object*, so you still must check for null and create if 
necessary.  The amount of code here is basically the same whether you 
use *in* or just aa[key].  Since you are using an object reference, you 
can change the object in the AA directly and there is no real need for 
pointers.

We now have 3 possibilities for AA which return classes:

1. Key is not in the AA, must check for this or Exception thrown.
2. Key is in the AA, but the reference stored there is null.
3. Key is in the AA, reference points to an object.

void CallMethod( MyClass c, int key, MyClass[int]  lookup )
{
   MyClass* ptr = (key in lookup);
   if ( ptr is null )  lookup[key] = new MyClass();

   MyClass ref = lookup[key];
   if ( ref is null )
   {
     ref = new MyClass();
     lookup[key]= ref;
   }
   ref.Method();
}

This is starting to make C++ look clean.

Structs on the other hand are supposed to be implicitly created.
They are *values* not objects.

So now we have 2 coding idioms/paradigms for structs:
1. Implicitly create when declared in code.

struct S {
	int n = 99;
	void callme(){ n++ ; }
}

S var1, var2, var3;  // created as declared
var1.callme();       // no problem, I want the defaults, I defined them

2. Struct value in an AA.

S[int] lookup;
S[100].callme() ; //Now this statement requires 7-10 lines of code.

All this because someone was concerned with a double lookup the *first 
time* an object isnt found in the AA?  Semantical madness over something 
that isnt even a real deficiency.

Premature optimization is a root cause of bad programming, because you 
end up making hard to follow code in attempts to optimize before you 
even know what is slow.

This is premature optimization at the *language level*.
-DavidM
Jul 12 2005
next sibling parent reply Shammah Chancellor <Shammah_member pathlink.com> writes:
In article <db0p66$vfj$1 digitaldaemon.com>, David Medlock says...
I have to say the AA changes are confusing to say the least.

With classes using  'lookup[key]'  would create an (key,value) *entry* 
but not an *object*, so you still must check for null and create if 
necessary.  The amount of code here is basically the same whether you 
use *in* or just aa[key].  Since you are using an object reference, you 
can change the object in the AA directly and there is no real need for 
pointers.

We now have 3 possibilities for AA which return classes:

1. Key is not in the AA, must check for this or Exception thrown.
2. Key is in the AA, but the reference stored there is null.
3. Key is in the AA, reference points to an object.

void CallMethod( MyClass c, int key, MyClass[int]  lookup )
{
   MyClass* ptr = (key in lookup);
   if ( ptr is null )  lookup[key] = new MyClass();

   MyClass ref = lookup[key];
   if ( ref is null )
   {
     ref = new MyClass();
     lookup[key]= ref;
   }
   ref.Method();
}

This is starting to make C++ look clean.

Structs on the other hand are supposed to be implicitly created.
They are *values* not objects.

So now we have 2 coding idioms/paradigms for structs:
1. Implicitly create when declared in code.

struct S {
	int n = 99;
	void callme(){ n++ ; }
}

S var1, var2, var3;  // created as declared
var1.callme();       // no problem, I want the defaults, I defined them

2. Struct value in an AA.

S[int] lookup;
S[100].callme() ; //Now this statement requires 7-10 lines of code.

All this because someone was concerned with a double lookup the *first 
time* an object isnt found in the AA?  Semantical madness over something 
that isnt even a real deficiency.

Premature optimization is a root cause of bad programming, because you 
end up making hard to follow code in attempts to optimize before you 
even know what is slow.

This is premature optimization at the *language level*.
-DavidM

Some of the code you mentioned has already been deprecated. aa[key] now throws an exception if it does not exist. Thus aa[newkey] = new Class is now invalid. You must instead call aa.Add(key) (See changes for DMD 126: Now throws an ArrayBoundsError if accessing an associative array with a key that is not already in the array. Previously, the key would be added to the array. ) Thus your code would change to something like this :try { : foo = aa[key]; :} catch ( Exception e ) { : foo = aa.Add(key, new MyClass()); :} finally { : foo.Method(); :} Error handling sucks. Personally I think AA syntax is starting to transcend integral types. Since when does an integer throw an exception? I'm all for making AA's a part of phobos and separating them from the language syntax. Also, these silly functions properties that are accessed like members are very confusing. Like this: void HelloWorld(int foo) { writeln("Hi world, I have %d foos", foo); } int foo; foo.HelloWorld(); ^-- AAARGGGH!!! Confusion ensues! I'd like to see this syntax go away too. It's not that much effort for people who want to extend the integer type to type this: HelloWorld(foo); (which is less typing By the way, notice the lack of the extra period) Which is how it belongs since it's not part of the integer type, and you can't find it in the class file for integer.
Jul 12 2005
next sibling parent David Medlock <noone nowhere.com> writes:
Shammah Chancellor wrote:
 In article <db0p66$vfj$1 digitaldaemon.com>, David Medlock says...
 
I have to say the AA changes are confusing to say the least.

With classes using  'lookup[key]'  would create an (key,value) *entry* 
but not an *object*, so you still must check for null and create if 
necessary.  The amount of code here is basically the same whether you 
use *in* or just aa[key].  Since you are using an object reference, you 
can change the object in the AA directly and there is no real need for 
pointers.

We now have 3 possibilities for AA which return classes:

1. Key is not in the AA, must check for this or Exception thrown.
2. Key is in the AA, but the reference stored there is null.
3. Key is in the AA, reference points to an object.

void CallMethod( MyClass c, int key, MyClass[int]  lookup )
{
  MyClass* ptr = (key in lookup);
  if ( ptr is null )  lookup[key] = new MyClass();

  MyClass ref = lookup[key];
  if ( ref is null )
  {
    ref = new MyClass();
    lookup[key]= ref;
  }
  ref.Method();
}

This is starting to make C++ look clean.

Structs on the other hand are supposed to be implicitly created.
They are *values* not objects.

So now we have 2 coding idioms/paradigms for structs:
1. Implicitly create when declared in code.

struct S {
	int n = 99;
	void callme(){ n++ ; }
}

S var1, var2, var3;  // created as declared
var1.callme();       // no problem, I want the defaults, I defined them

2. Struct value in an AA.

S[int] lookup;
S[100].callme() ; //Now this statement requires 7-10 lines of code.

All this because someone was concerned with a double lookup the *first 
time* an object isnt found in the AA?  Semantical madness over something 
that isnt even a real deficiency.

Premature optimization is a root cause of bad programming, because you 
end up making hard to follow code in attempts to optimize before you 
even know what is slow.

This is premature optimization at the *language level*.
-DavidM

Some of the code you mentioned has already been deprecated. aa[key] now throws an exception if it does not exist. Thus aa[newkey] = new Class is now invalid. You must instead call aa.Add(key)

Where is this documented? http://www.digitalmars.com/d/arrays.html#associative I see examples of int[char[]] b; // associative array b of ints that are // indexed by an array of characters. // The KeyType is char[] b["hello"] = 3; // set value associated with key "hello" to 3
 (See changes for DMD 126: Now throws an ArrayBoundsError if accessing an
 associative array with a key that is not already in the array. Previously, the
 key would be added to the array. )
 
 Thus your code would change to something like this
 
 :try {
 :    foo = aa[key];
 :} catch ( Exception e ) {
 :    foo = aa.Add(key, new MyClass());
 :} finally {
 :    foo.Method();
 :}
 
 Error handling sucks.  Personally I think AA syntax is starting to transcend
 integral types.  Since when does an integer throw an exception?  I'm all for
 making AA's a part of phobos and separating them from the language syntax.  

That is better than the previous semantics? Compared to what? Java? I thought D was supposed to be attracting C/C++ programmers. Now we have checked exceptions?
 
 
 Also, these silly functions properties that are accessed like members are very
 confusing.   Like this:
 
 
 void HelloWorld(int foo) {
 writeln("Hi world, I have %d foos", foo);
 }
 
 int foo;
 
 foo.HelloWorld();
 
 ^-- AAARGGGH!!!  Confusion ensues!  I'd like to see this syntax go away too.
 It's not that much effort for people who want to extend the integer type to
type
 this:
 
 HelloWorld(foo);  (which is less typing By the way, notice the lack of the
extra
 period)
 
 Which is how it belongs since it's not part of the integer type, and you can't
 find it in the class file for integer. 
 
 
 

I agree you can go overboard with built in methods, however for templates they are more valuable because they can be overloaded in a class, whereas you cannot overload a specialized operator like *in*.
Jul 12 2005
prev sibling parent Chris Sauls <ibisbasenji gmail.com> writes:
Shammah Chancellor wrote:
 aa[key] now throws an exception if it does not exist.  Thus aa[newkey] = new
 Class is now invalid.  You must instead call aa.Add(key) 

There is no exception thrown if aa[newkey] is an lvalue. I have tested this with DMD 0.128, which also errored when I tried using this .Add(key) syntax you mention. As far as I know, the way to add a key to an AA is still: # aa[key] = value; -- Chris Sauls PS: Sample test program. # import std.stdio; # # int main (in char[][] args) { # int[char[]] map; # # // map.Add("foo"); // <-- This does not compile. # map["bar"] = 256; # foreach (char[] key, int value; map) # writefln("%s: %s", key, value); # return 0; # }
Jul 13 2005
prev sibling next sibling parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
 void CallMethod( MyClass c, int key, MyClass[int]  lookup )
 {
   MyClass* ptr = (key in lookup);
   if ( ptr is null )  lookup[key] = new MyClass();

   MyClass ref = lookup[key];
   if ( ref is null )
   {
     ref = new MyClass();
     lookup[key]= ref;
   }
   ref.Method();
 }

Once the bug that you found with & is fixed the above can be rewritten as void CallMethod( int key, MyClass[int] lookup ) { MyClass* ptr = &lookup[key]; if ( !*ptr ) *ptr = new MyClass; *ptr.Method(); }
 This is starting to make C++ look clean.

Again assuming the & bug is fixed the C++ code map<A,B> x; B& ref = x[a]; can be rewritten to the D code B[A] x; B* ref = &x[a]; That is about as simple as one can expect.
 Structs on the other hand are supposed to be implicitly created.
 They are *values* not objects.

 So now we have 2 coding idioms/paradigms for structs:
 1. Implicitly create when declared in code.

 struct S {
 int n = 99;
 void callme(){ n++ ; }
 }

 S var1, var2, var3;  // created as declared
 var1.callme();       // no problem, I want the defaults, I defined them

 2. Struct value in an AA.

 S[int] lookup;
 S[100].callme() ; //Now this statement requires 7-10 lines of code.

 All this because someone was concerned with a double lookup the *first 
 time* an object isnt found in the AA?  Semantical madness over something 
 that isnt even a real deficiency.

 Premature optimization is a root cause of bad programming, because you end 
 up making hard to follow code in attempts to optimize before you even know 
 what is slow.

 This is premature optimization at the *language level*.
 -DavidM

Somewhere I had read that C++ chose the lookup-and-insert behavior because throwing an exception was too slow and has spotty support. In any case I know BS said that operator[] returns a reference because returning the value is too slow. So there are a couple of C++ examples where performance drove the design.
Jul 12 2005
next sibling parent reply David Medlock <noone nowhere.com> writes:
Ben Hinkle wrote:

void CallMethod( MyClass c, int key, MyClass[int]  lookup )
{
  MyClass* ptr = (key in lookup);
  if ( ptr is null )  lookup[key] = new MyClass();

  MyClass ref = lookup[key];
  if ( ref is null )
  {
    ref = new MyClass();
    lookup[key]= ref;
  }
  ref.Method();
}

Once the bug that you found with & is fixed the above can be rewritten as void CallMethod( int key, MyClass[int] lookup ) { MyClass* ptr = &lookup[key]; if ( !*ptr ) *ptr = new MyClass; *ptr.Method(); }
This is starting to make C++ look clean.

Again assuming the & bug is fixed the C++ code map<A,B> x; B& ref = x[a]; can be rewritten to the D code B[A] x; B* ref = &x[a]; That is about as simple as one can expect.

Besides you haven't shown how that is superior to the old way. What if I want value semantics and not call by reference? (Which is the whole point of using a struct vs a class ) B* ref = &x[a]; B temp = ref[0]; // now I can use my local copy and reinsert it if needed // versus B temp = x[a]; The point is the change as made the code *more* complex, with zero benefits whatsoever. If the double lookup is a non-issue because it only matters when the value is null (ie the first time). If you look up the *in stinks* thread, Matthew starts with complaints that the in operator returned a pointer. I actually agree with his sentiment, foreach(collections), object references(classes) and out parameters(for structs and other values) are superior to pointers. http://www.digitalmars.com/d/archives/digitalmars/D/18450.html Now pointers are *required*, and the code to access structures is longer(my example) or messy(your example)!! The better alternative to *in* is a method: B value; if ( x.get( in a, out value ) ) { value.dostuff(); } That is as simple as it gets, no pointers, can still use the create semantics, and if 'x' is a template alias, the x can implement .get(...) and still function with the don't create semantics. I can't overload your example. -DavidM
Jul 12 2005
next sibling parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"David Medlock" <noone nowhere.com> wrote in message 
news:db0vub$15ot$1 digitaldaemon.com...
 Ben Hinkle wrote:

void CallMethod( MyClass c, int key, MyClass[int]  lookup )
{
  MyClass* ptr = (key in lookup);
  if ( ptr is null )  lookup[key] = new MyClass();

  MyClass ref = lookup[key];
  if ( ref is null )
  {
    ref = new MyClass();
    lookup[key]= ref;
  }
  ref.Method();
}

Once the bug that you found with & is fixed the above can be rewritten as void CallMethod( int key, MyClass[int] lookup ) { MyClass* ptr = &lookup[key]; if ( !*ptr ) *ptr = new MyClass; *ptr.Method(); }
This is starting to make C++ look clean.

Again assuming the & bug is fixed the C++ code map<A,B> x; B& ref = x[a]; can be rewritten to the D code B[A] x; B* ref = &x[a]; That is about as simple as one can expect.

Besides you haven't shown how that is superior to the old way.

I'm not sure what you mean by "old way". Can you be more specific?
 What if I want value semantics and not call by reference?
 (Which is the whole point of using a struct vs a class )
 B* ref = &x[a];
 B  temp = ref[0];
   // now I can use my local copy and reinsert it if needed

 // versus

 B temp = x[a];

 The point is the change as made the code *more* complex, with zero 
 benefits whatsoever.  If the double lookup is a non-issue because it only 
 matters when the value is null (ie the first time).

umm - "zero benefit"? whatever... I agree it would be nice to have an explicit method to insert-and-lookup when that is what one wants as was discussed in the posts on the bug thread about &.
 If you look up the *in stinks* thread, Matthew starts with complaints that 
 the in operator returned a pointer.  I actually agree with his sentiment, 
 foreach(collections), object references(classes) and out parameters(for 
 structs and other values) are superior to pointers.

 http://www.digitalmars.com/d/archives/digitalmars/D/18450.html

 Now pointers are *required*, and the code to access structures is 
 longer(my example) or messy(your example)!!

 The better alternative to *in* is a method:

 B value;
 if ( x.get( in a, out value ) ) { value.dostuff(); }

 That is as simple as it gets, no pointers, can still use the create 
 semantics, and if 'x' is a template alias, the x can implement .get(...) 
 and still function with the don't create semantics.

Various posts (I included it in my API requests) had what you call "get" by the name "contains" (though to be honest I've forgotten the details). I personally don't have anything against pointers, though, so the current 'in' is ok with me.
 I can't overload your example.

I, too, would like more overload control than what is available today. See the posts about opIndexMutable and such started by Kevin Bealer, for example.
Jul 12 2005
parent reply David Medlock <noone nowhere.com> writes:
Ben Hinkle wrote:

 "David Medlock" <noone nowhere.com> wrote in message 
The point is the change as made the code *more* complex, with zero 
benefits whatsoever.  If the double lookup is a non-issue because it only 
matters when the value is null (ie the first time).

umm - "zero benefit"? whatever...

Since you see a benefit, please tell me what it is. It still escapes me. -DavidM
Jul 12 2005
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"David Medlock" <noone nowhere.com> wrote in message 
news:db11io$174u$1 digitaldaemon.com...
 Ben Hinkle wrote:

 "David Medlock" <noone nowhere.com> wrote in message
The point is the change as made the code *more* complex, with zero 
benefits whatsoever.  If the double lookup is a non-issue because it only 
matters when the value is null (ie the first time).

umm - "zero benefit"? whatever...

Since you see a benefit, please tell me what it is. It still escapes me. -DavidM

* more intuitive for most users (ie - those not assuming AAs are stl::map) * more like Java, C#, Ruby (which return null and only work for classes) and Python (which errors) * means regular rvalue lookup does not modify the array which means reading is thread-safe * consistent with dynamic and static arrays where requesting a value not in the array throws
Jul 12 2005
next sibling parent reply David Medlock <noone nowhere.com> writes:
Ben Hinkle wrote:

  > * more intuitive for most users (ie - those not assuming AAs are 
stl::map)
This is subjective of course, but aren't most D ppl coming from C++?

See my post about call-by-value vs call-by-reference. We added 1 new 
condition for struct/values and 1 new condition for classes.
More intuitive for a total of 5 conditions vs 3 before?

IMO declaring:

MyClass[int] aa;
MyClass c = aa[100];

should give me the default for an uninitialized MyClass object, null.
I don't think an Exception is warranted, because I expect some values 
won't exist yet, but that does not make the key invalid!

 * more like Java, C#, Ruby (which return null and only work for classes) and 
 Python (which errors)

non-classes. Their containers are themselves classes, which could be implemented in D also. Python errors , while Lua returns null: -- lua code a = lookup[100] or createObject(); -- very succinct -- Plus python has the get(..) method we discussed. Not much Python code try...catches around the lookup I'd bet.
 * means regular rvalue lookup does not modify the array which means reading 
 is thread-safe

thread safety should dictate the lowest common denominator for a language which is already pretty wide open, thread-safety wise.
 * consistent with dynamic and static arrays where requesting a value not in 
 the array throws 

normal execution. I would hope D isn't moving towards checked Exceptions. In any case, I sincerely hope Walter adds a get()/lookup() builtin. Thanks for the points, Ben. Please do not mistake my passion for a pragmatic D as anything else. -DavidM
Jul 12 2005
next sibling parent reply "Andrew Fedoniouk" <news terrainformatica.com> writes:
"David Medlock" <noone nowhere.com> wrote in message 
news:db15vt$1aqu$1 digitaldaemon.com...
 Ben Hinkle wrote:

  > * more intuitive for most users (ie - those not assuming AAs are 
 stl::map)
 This is subjective of course, but aren't most D ppl coming from C++?

 See my post about call-by-value vs call-by-reference. We added 1 new 
 condition for struct/values and 1 new condition for classes.
 More intuitive for a total of 5 conditions vs 3 before?

 IMO declaring:

 MyClass[int] aa;
 MyClass c = aa[100];

 should give me the default for an uninitialized MyClass object, null.
 I don't think an Exception is warranted, because I expect some values 
 won't exist yet, but that does not make the key invalid!

 * more like Java, C#, Ruby (which return null and only work for classes) 
 and Python (which errors)

non-classes. Their containers are themselves classes, which could be implemented in D also. Python errors , while Lua returns null: -- lua code a = lookup[100] or createObject(); -- very succinct -- Plus python has the get(..) method we discussed. Not much Python code try...catches around the lookup I'd bet.
 * means regular rvalue lookup does not modify the array which means 
 reading is thread-safe

thread safety should dictate the lowest common denominator for a language which is already pretty wide open, thread-safety wise.

Agree.
 * consistent with dynamic and static arrays where requesting a value not 
 in the array throws

normal execution. I would hope D isn't moving towards checked Exceptions.

Agree. "key does not exist" is always expected result for AA. And constant/immutable AAs must have only 'in' (or immutable get) method available to caller.
 In any case, I sincerely hope Walter adds a get()/lookup() builtin.

 Thanks for the points, Ben.

 Please do not mistake my passion for a pragmatic D as anything else.
 -DavidM 

Jul 12 2005
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
 * consistent with dynamic and static arrays where requesting a value not 
 in the array throws

normal execution. I would hope D isn't moving towards checked Exceptions.

Agree. "key does not exist" is always expected result for AA. And constant/immutable AAs must have only 'in' (or immutable get) method available to caller.

I don't actually mind returning init on failure but my preference is throwing. I think Matthew was the biggest proponent of throwing - but my memory is vague. Here's a silly example but imagine an AA indexed by country with enum {Invade, Sanctions} and you look up, say, Iraq and no-one had thought about what to do about Iraq yet. That might actually explain a few things. :-P
Jul 12 2005
parent reply "Andrew Fedoniouk" <news terrainformatica.com> writes:
"Ben Hinkle" <bhinkle mathworks.com> wrote in message 
news:db19ia$1ead$1 digitaldaemon.com...
 * consistent with dynamic and static arrays where requesting a value 
 not in the array throws

normal execution. I would hope D isn't moving towards checked Exceptions.

Agree. "key does not exist" is always expected result for AA. And constant/immutable AAs must have only 'in' (or immutable get) method available to caller.

I don't actually mind returning init on failure but my preference is throwing. I think Matthew was the biggest proponent of throwing - but my memory is vague. Here's a silly example but imagine an AA indexed by country with enum {Invade, Sanctions} and you look up, say, Iraq and no-one had thought about what to do about Iraq yet. That might actually explain a few things. :-P

:) Well, Canada prefer different enum values {Friend, Almost, NotYet}otherwise example is good. There are two distinct situations: 1) immutable AA - List of keywords in compiler. It shouldn't be such entities as opIndex and opIndexAssign at all. Right? 2) dynamic AA - E.g. word counting: Both opIndex and opIndexAssign should be there. dictionary[word]++; in wc2.d looked more elegantly than if (!(word in dictionary)) dictionary[word] = 0; else ++dictionary[word]; isn't it? Andrew.
Jul 12 2005
next sibling parent Concerned <Concerned_member pathlink.com> writes:
I am beginning to like

int K[char[] : loose]
and
int U[char[] : strict]

K["notpresentkey"]  would return  "" that is the init (but not create element)
and
U["notpresentkey"]  would rise an error

as in digitalmars.D/26519


By the way the word count example (that you mentioned) is a very good one. And
also if I wanted to know how many words "apple" there where, then 0 (the init)
would be the right answer.

Thank you!

In article <db1erq$1jca$1 digitaldaemon.com>, Andrew Fedoniouk says...
"Ben Hinkle" <bhinkle mathworks.com> wrote in message 
news:db19ia$1ead$1 digitaldaemon.com...
 * consistent with dynamic and static arrays where requesting a value 
 not in the array throws

normal execution. I would hope D isn't moving towards checked Exceptions.

Agree. "key does not exist" is always expected result for AA. And constant/immutable AAs must have only 'in' (or immutable get) method available to caller.

I don't actually mind returning init on failure but my preference is throwing. I think Matthew was the biggest proponent of throwing - but my memory is vague. Here's a silly example but imagine an AA indexed by country with enum {Invade, Sanctions} and you look up, say, Iraq and no-one had thought about what to do about Iraq yet. That might actually explain a few things. :-P

:) Well, Canada prefer different enum values {Friend, Almost, NotYet}otherwise example is good. There are two distinct situations: 1) immutable AA - List of keywords in compiler. It shouldn't be such entities as opIndex and opIndexAssign at all. Right? 2) dynamic AA - E.g. word counting: Both opIndex and opIndexAssign should be there. dictionary[word]++; in wc2.d looked more elegantly than if (!(word in dictionary)) dictionary[word] = 0; else ++dictionary[word]; isn't it? Andrew.

Jul 12 2005
prev sibling parent "Regan Heath" <regan netwin.co.nz> writes:
On Tue, 12 Jul 2005 15:05:46 -0700, Andrew Fedoniouk  
<news terrainformatica.com> wrote:
 There are two distinct situations:
 1)  immutable AA - List of keywords in compiler. It shouldn't be such
 entities as opIndex and opIndexAssign at all. Right?
 2) dynamic AA - E.g. word counting:  Both opIndex and opIndexAssign  
 should
 be there.

 dictionary[word]++;

 in wc2.d looked more elegantly than

 if (!(word in dictionary))
    dictionary[word] = 0;
 else
    ++dictionary[word];

 isn't it?

import std.stdio; void main() { int[char[]] dictionary; dictionary["regan"]++; writefln(dictionary["regan"]); } works fine. The change only effects: int i = dictionary["regan"]; usage an an rvalue (is that the correct terminology?) Regan
Jul 12 2005
prev sibling parent reply =?ISO-8859-1?Q?Anders_F_Bj=F6rklund?= <afb algonet.se> writes:
David Medlock wrote:

 IMO declaring:
 
 MyClass[int] aa;
 MyClass c = aa[100];
 
 should give me the default for an uninitialized MyClass object, null.
 I don't think an Exception is warranted, because I expect some values 
 won't exist yet, but that does not make the key invalid!

I was arguing for the same thing, back when it inserted a "zero" entry instead of throwing a strange "OutOfBounds" exception - like it is now. The current behaviour has the implicit side affect of defining an AA to only the current keys, which I think is a very sad definition of it ? Anyway, the work-around to this is still the same as it was before... Do a double lookup, once to see if the key exists and twice to get it. MyClass c = (100 in aa) ? aa[100] : int.init; --anders
Jul 13 2005
parent reply "Regan Heath" <regan netwin.co.nz> writes:
On Wed, 13 Jul 2005 11:26:43 +0200, Anders F Björklund <afb algonet.se>  
wrote:
 David Medlock wrote:

 IMO declaring:
  MyClass[int] aa;
 MyClass c = aa[100];
  should give me the default for an uninitialized MyClass object, null.
 I don't think an Exception is warranted, because I expect some values  
 won't exist yet, but that does not make the key invalid!

I was arguing for the same thing, back when it inserted a "zero" entry instead of throwing a strange "OutOfBounds" exception - like it is now. The current behaviour has the implicit side affect of defining an AA to only the current keys, which I think is a very sad definition of it ? Anyway, the work-around to this is still the same as it was before... Do a double lookup, once to see if the key exists and twice to get it. MyClass c = (100 in aa) ? aa[100] : int.init;

Or, using one lookup: try { c = aa[100]; } catch(ArrayBoundsError e) { c = new MyClass(); } Regan
Jul 13 2005
parent =?ISO-8859-15?Q?Anders_F_Bj=F6rklund?= <afb algonet.se> writes:
Regan Heath wrote:

 Anyway, the work-around to this is still the same as it was before...
 Do a double lookup, once to see if the key exists and twice to get it.

 MyClass c = (100 in aa) ? aa[100] : int.init;

Or, using one lookup: try { c = aa[100]; } catch(ArrayBoundsError e) { c = new MyClass(); }

There are two down-sides to this try/catch approach: 1) I think it might be slower, but haven't tested that... 2) It won't work in -release mode, no ArrayBoundsErrors ? In fact, with GDC I even get an access error when trying to retrieve a key outside the ones I've explicitly defined. So I think I'll stick with the old bandaid, thank you :-) --anders
Jul 13 2005
prev sibling parent reply Nick <Nick_member pathlink.com> writes:
In article <db12ll$17tq$1 digitaldaemon.com>, Ben Hinkle says...
* more intuitive for most users (ie - those not assuming AAs are stl::map)
* more like Java, C#, Ruby (which return null and only work for classes) and 
Python (which errors)
* means regular rvalue lookup does not modify the array which means reading 
is thread-safe
* consistent with dynamic and static arrays where requesting a value not in 
the array throws 

I, for one, agree with you. It can also help catch lots of bugs, such as typos, ie: somelist["height"] = 10; .. int height = somelist["heigth"]; // Should complain rather than return zero This is why, after thinking about it, I am really not in favor of adding more special cases for adding elements, such as & or accessing struct members. What we need instead (IMO) is a clearer way for the programmer to specify whether (s)he wants to only read existing elements, or to add them if they doesn't exist. Nick
Jul 13 2005
parent reply David Medlock <noone nowhere.com> writes:
Nick wrote:
 In article <db12ll$17tq$1 digitaldaemon.com>, Ben Hinkle says...
 
* more intuitive for most users (ie - those not assuming AAs are stl::map)
* more like Java, C#, Ruby (which return null and only work for classes) and 
Python (which errors)
* means regular rvalue lookup does not modify the array which means reading 
is thread-safe
* consistent with dynamic and static arrays where requesting a value not in 
the array throws 

I, for one, agree with you. It can also help catch lots of bugs, such as typos, ie: somelist["height"] = 10; .. int height = somelist["heigth"]; // Should complain rather than return zero

If that is a constant, it should be stored in one. In cases where it is computed or passed as a parameter it will already be in a variable. In any case, I don't think Walter should limit the capabilities of the language based around possible programmer typos. -DavidM
Jul 13 2005
parent reply sq <sq_member pathlink.com> writes:
In article <db302m$629$2 digitaldaemon.com>, David Medlock says...

[snip]

In any case, I don't think Walter should limit the capabilities of the 
language based around possible programmer typos.

And so should he change it to allow "if (expr);" and "if (a=b)" ?
Jul 13 2005
parent David Medlock <noone nowhere.com> writes:
sq wrote:
 In article <db302m$629$2 digitaldaemon.com>, David Medlock says...
 
 [snip]
 
 
In any case, I don't think Walter should limit the capabilities of the 
language based around possible programmer typos.

And so should he change it to allow "if (expr);" and "if (a=b)" ?

Nothing in the spec disallows the first one, its not allowed in Walter's implementation. I can prove the second is lint-checking too: bool b = ( a = true ); if ( a = true ) writefln("Hello"); on the first line ( a = true ) evaluates to a boolean. On the second line the same expression does not.... Your example is syntax, the discussion is semantics. -DavidM
Jul 13 2005
prev sibling parent Shammah Chancellor <Shammah_member pathlink.com> writes:
That's beginning to look like C++.
Besides you haven't shown how that is superior to the old way.

What if I want value semantics and not call by reference?
(Which is the whole point of using a struct vs a class )
B* ref = &x[a];
B  temp = ref[0];
   // now I can use my local copy and reinsert it if needed

// versus

B temp = x[a];

The point is the change as made the code *more* complex, with zero 
benefits whatsoever.  If the double lookup is a non-issue because it 
only matters when the value is null (ie the first time).

If you look up the *in stinks* thread, Matthew starts with complaints 
that the in operator returned a pointer.  I actually agree with his 
sentiment, foreach(collections), object references(classes) and out 
parameters(for structs and other values) are superior to pointers.

http://www.digitalmars.com/d/archives/digitalmars/D/18450.html

Now pointers are *required*, and the code to access structures is 
longer(my example) or messy(your example)!!

The better alternative to *in* is a method:

B value;
if ( x.get( in a, out value ) ) { value.dostuff(); }

That is as simple as it gets, no pointers, can still use the create 
semantics, and if 'x' is a template alias, the x can implement .get(...) 
and still function with the don't create semantics.
I can't overload your example.

-DavidM

Not to mention we have some context dependant syntax going on there with the in keyword being used for different things in different places. IE: int foo( in int a, inout int[int] foo ) { MyClass *arg = a in foo; } See the different use? I vote to make AA it's own class....
Jul 12 2005
prev sibling parent reply Shammah Chancellor <Shammah_member pathlink.com> writes:
In article <db0ucs$14fp$1 digitaldaemon.com>, Ben Hinkle says...
 void CallMethod( MyClass c, int key, MyClass[int]  lookup )
 {
   MyClass* ptr = (key in lookup);
   if ( ptr is null )  lookup[key] = new MyClass();

   MyClass ref = lookup[key];
   if ( ref is null )
   {
     ref = new MyClass();
     lookup[key]= ref;
   }
   ref.Method();
 }

Once the bug that you found with & is fixed the above can be rewritten as void CallMethod( int key, MyClass[int] lookup ) { MyClass* ptr = &lookup[key]; if ( !*ptr ) *ptr = new MyClass; *ptr.Method(); }

This will no longer work as lookup[key] will throw an exception since of creating the key since DMD 0.126. If Add will replace existing values, or if there is a Replace method then you could still simplify it to something very close to what you have.
Jul 12 2005
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
Once the bug that you found with & is fixed the above can be rewritten as
void CallMethod( int key, MyClass[int]  lookup )
{
   MyClass* ptr = &lookup[key];
   if ( !*ptr ) *ptr = new MyClass;
   *ptr.Method();
}

This will no longer work as lookup[key] will throw an exception since of creating the key since DMD 0.126. If Add will replace existing values, or if there is a Replace method then you could still simplify it to something very close to what you have.

lookup[key] does not throw if the result is expected to be an lvalue. The problem is that there is a bug in dmd that the & operator isn't among those operators that are flagged as expecting an lvalue. See David's earlier post in this newsgroup and see my post about it in the bugs newsgroup.
Jul 12 2005
parent reply Shammah Chancellor <Shammah_member pathlink.com> writes:
In article <db118h$16qm$1 digitaldaemon.com>, Ben Hinkle says...
Once the bug that you found with & is fixed the above can be rewritten as
void CallMethod( int key, MyClass[int]  lookup )
{
   MyClass* ptr = &lookup[key];
   if ( !*ptr ) *ptr = new MyClass;
   *ptr.Method();
}

This will no longer work as lookup[key] will throw an exception since of creating the key since DMD 0.126. If Add will replace existing values, or if there is a Replace method then you could still simplify it to something very close to what you have.

lookup[key] does not throw if the result is expected to be an lvalue. The problem is that there is a bug in dmd that the & operator isn't among those operators that are flagged as expecting an lvalue. See David's earlier post in this newsgroup and see my post about it in the bugs newsgroup.

Hrm.. It didn't say that in the changelog, but i'll believe you. (See the changelog for DMD 0.126) Shouldn't this work: void CallMethod( MyClass c, int key, MyClass[int] lookup ) { MyClass *ref = key in lookup ; MyClass ptr; if ( !ref || !*ref ) lookup[key] = ptr = new MyClass(); else ptr = *ref; ptr.Method(); } I can't really see a better solution. Adding or ignoring if exists doesn't seem good. IE: aa.CreateIfNotExists( key, new MyClass() ); This will always create a new MyClass even if there is no new key made.
Jul 12 2005
parent "Ben Hinkle" <bhinkle mathworks.com> writes:
"Shammah Chancellor" <Shammah_member pathlink.com> wrote in message 
news:db14mg$19qo$1 digitaldaemon.com...
 In article <db118h$16qm$1 digitaldaemon.com>, Ben Hinkle says...
Once the bug that you found with & is fixed the above can be rewritten 
as
void CallMethod( int key, MyClass[int]  lookup )
{
   MyClass* ptr = &lookup[key];
   if ( !*ptr ) *ptr = new MyClass;
   *ptr.Method();
}

This will no longer work as lookup[key] will throw an exception since of creating the key since DMD 0.126. If Add will replace existing values, or if there is a Replace method then you could still simplify it to something very close to what you have.

lookup[key] does not throw if the result is expected to be an lvalue. The problem is that there is a bug in dmd that the & operator isn't among those operators that are flagged as expecting an lvalue. See David's earlier post in this newsgroup and see my post about it in the bugs newsgroup.

Hrm.. It didn't say that in the changelog, but i'll believe you. (See the changelog for DMD 0.126)

The details about AAs are still largely undocumented, that's true. Walter hasn't been very vocal about where he intends to go with AAs but that isn't unusual.
 Shouldn't this work:

 void CallMethod( MyClass c, int key, MyClass[int]  lookup )
 {
 MyClass *ref = key in lookup ;
 MyClass ptr;

 if ( !ref || !*ref )  lookup[key] = ptr = new MyClass();
 else ptr = *ref;
 ptr.Method();
 }

 I can't really see a better solution.  Adding or ignoring if exists 
 doesn't seem
 good.  IE:  aa.CreateIfNotExists( key, new MyClass() );  This will always 
 create
 a new MyClass even if there is no new key made.

Correct - that will work today. However I still hope Walter makes &lookup[key] insert if not present.
Jul 12 2005
prev sibling next sibling parent David Medlock <noone nowhere.com> writes:
Here is an example of code which was elegant, and now must be messied up 
because of the changes in AA(at bottom).

My choices now are to
A) switch everyhing to pointers and use &keys[...]
B) add functions which create when needed in the arrays
C) use a function to add all the keys on startup (Not feasible, as keys 
are updated as they are queried)

I think the code below is fairly elegant, but it will not be under the 
new AA.


// This code uses the glfw framework to check Keys and Mouse Movement
import glfw;

import std.stdio;

public int                   MouseX, MouseY, MouseZ, MouseDX, MouseDY, 
MouseDZ;


enum Mouse { Left=GLFW_MOUSE_BUTTON_LEFT, Right=GLFW_MOUSE_BUTTON_RIGHT, 
Middle=GLFW_MOUSE_BUTTON_MIDDLE };
enum Key { F1=GLFW_KEY_F1, F2=GLFW_KEY_F2, F3=GLFW_KEY_F3, F4=GLFW_KEY_F4 };


private:
struct keystate
{
   bool    down      = false;
   bool    changed   = false;
   bool    updated   = false;

   bool    released() { return (!down) && changed; }
   bool    pressed()  { return down && changed; }

   protected void update( bool isdown )
   {
     changed = ( down != isdown );
     down = isdown;
     updated = true;
   }
}


keystate[ int ]         keys;
keystate[ int ]         mbutton;


// mouse buttons are updated each Update, so just return the state
keystate    getmouse( int btn ){ return mbutton[ btn ]; }

keystate    getkey( int keycode )
{
   keystate st = keys[keycode];
   if ( !st.updated ) {
     st.update( glfwGetKey( keycode )==GLFW_PRESS );
     keys[keycode] = st;
   }
   return st;
}


public:

void ClearInput()
{
   foreach( int code; keys.keys ) delete keys[code];
   foreach( int code; mbutton.keys ) delete mbutton[code];
   MouseDX = MouseDY = MouseDZ = 0;
}

void CheckInput()
{
   foreach( int kcode; keys.keys )  (kcode in keys).updated = false;

   glfwPollEvents();

   int x, y, z;

   mbutton[Mouse.Left].update( glfwGetMouseButton( Mouse.Left 
)==GLFW_PRESS );
   mbutton[Mouse.Middle].update( glfwGetMouseButton( Mouse.Middle 
)==GLFW_PRESS );
   mbutton[Mouse.Right].update( glfwGetMouseButton( Mouse.Right 
)==GLFW_PRESS );

   z = glfwGetMouseWheel();
   glfwGetMousePos( &x, &y );
   MouseDX = x - MouseX;
   MouseDY = y - MouseY;
   MouseDZ = z - MouseZ;
   MouseX = x;
   MouseY = y;
   MouseZ = z;
}


// key was just pressed
bool    KeyPressed( int code ) { return getkey( code ).pressed; }

// key was just released
bool    KeyReleased( int code ) { return getkey( code ).released;}

// key is currently down
bool    KeyDown( int code ) { return getkey( code ).down ; }

// key is currently up
bool    KeyUp( int code )   { return getkey( code ).down==false; }


// mouse has moved since last update
bool    MouseMove() { return (MouseDX!=0) || (MouseDY!=0) || (MouseDZ!=0 
); }

bool    MouseDown(int btn) { return getmouse( btn ).down; }
bool    MouseUp( int btn ) { return false==getmouse( btn ).down; }
bool    MouseReleased( int btn ) { return getmouse(btn).released; }
bool    MousePressed( int btn )  { return getmouse(btn).pressed; }
Jul 12 2005
prev sibling next sibling parent reply "Andrew Fedoniouk" <news terrainformatica.com> writes:
Generally speaking effective AA implementation should have three methods:

bool aa.search( key , &value );
-- const (immutable) method.
-- D: key in aa;

aa.insert( key, value );
-- mutable method, 1) replaces value under
   the key if it is there or 2) inserts new key.value
   pair into the assosiation if does not exist.
-- D: aa[key] = value;

value aa.get( key, function(key,value) ctor );
-- mutable method, if value is not in collection
    calls ctor to construct value for the new pair.
    always returns initialized value.
-- ???

This is canonically full set of operations.

In presence of default initializers for types in D last 'get'
method could be substituted by x = aa[key] with
silent construction of new value if needed.
Not so convenient but reasonable compromise.

Andrew.
Jul 12 2005
next sibling parent "Andrew Fedoniouk" <news terrainformatica.com> writes:
Forgot to mention:

To be consistent with Ben's requirement:
"* consistent with dynamic and static arrays where requesting a value not in
the array throws"

AA should have read-only equivalent - read-only AA.
such AA# has method x = aa[key] implemented differently -
it throws if key is not in collection. 'insert' method shall be disabled and
and any attempt to call must be prevented at compile time.

Andrew.
Jul 12 2005
prev sibling parent reply Charles Hixson <charleshixsn earthlink.net> writes:
Andrew Fedoniouk wrote:
 Generally speaking effective AA implementation should have three methods:
 
 bool aa.search( key , &value );
 -- const (immutable) method.
 -- D: key in aa;
 
 aa.insert( key, value );
 -- mutable method, 1) replaces value under
    the key if it is there or 2) inserts new key.value
    pair into the assosiation if does not exist.
 -- D: aa[key] = value;
 
 value aa.get( key, function(key,value) ctor );
 -- mutable method, if value is not in collection
     calls ctor to construct value for the new pair.
     always returns initialized value.
 -- ???
 
 This is canonically full set of operations.
 
 In presence of default initializers for types in D last 'get'
 method could be substituted by x = aa[key] with
 silent construction of new value if needed.
 Not so convenient but reasonable compromise.
 
 Andrew.
  


set. An optimal set should have two more: 1) aa.create( key, value ); -- immutable method, 1) the key must not exist. Inserts new key.value pair into the association. Or fails (either returning null or throwing an error. 2) value aa.get( key); -- immutable method, fails if value is not in collection. Although this is a good set of operators, the syntax isn't pretty. One characteristic that a good syntax would have is that it would always be possible to guarantee that the object could be read without being changed. For that reason the expression, e.g.: if (aa["item"]) should be guaranteed to NOT insert "item" into aa. Syntax is always tricky, and a clear syntax is difficult.
Jul 12 2005
parent "Andrew Fedoniouk" <news terrainformatica.com> writes:
"Charles Hixson" <charleshixsn earthlink.net> wrote in message 
news:db1v3l$1v8v$1 digitaldaemon.com...
 Andrew Fedoniouk wrote:
 Generally speaking effective AA implementation should have three methods:

 bool aa.search( key , &value );
 -- const (immutable) method.
 -- D: key in aa;

 aa.insert( key, value );
 -- mutable method, 1) replaces value under
    the key if it is there or 2) inserts new key.value
    pair into the assosiation if does not exist.
 -- D: aa[key] = value;

 value aa.get( key, function(key,value) ctor );
 -- mutable method, if value is not in collection
     calls ctor to construct value for the new pair.
     always returns initialized value.
 -- ???

 This is canonically full set of operations.

 In presence of default initializers for types in D last 'get'
 method could be substituted by x = aa[key] with
 silent construction of new value if needed.
 Not so convenient but reasonable compromise.

 Andrew.


optimal set should have two more: 1) aa.create( key, value ); -- immutable method, 1) the key must not exist. Inserts new key.value pair into the association. Or fails (either returning null or throwing an error.

Yep. Nice to have but is quite rare I guess. PS: it is definitely mutable.
 2) value aa.get( key);
  -- immutable method, fails if value is not in collection.

this is what exactly 'search' (in) does but without throwing exception.
 Although this is a good set of operators, the syntax isn't pretty.  One 
 characteristic that a good syntax would have is that it would always be 
 possible to guarantee that the object could be read without being changed. 
 For that reason the expression, e.g.:
 if (aa["item"])
 should be guaranteed to NOT insert "item" into aa.

This will be naturally solvable if it would be possible to declare read-only arrays.
 Syntax is always tricky, and a clear syntax is difficult. 

Jul 12 2005
prev sibling parent me <me_member pathlink.com> writes:
In article <db0p66$vfj$1 digitaldaemon.com>, David Medlock says...

I have to say the AA changes are confusing to say the least ...

The current semantics helps coders guard against mistakes. Rather than assuming that an AA entry exists, it forces coders to take explicit responsibility for that. As such, your angst could be reduced by acknowledging this paradigm and working with it. In your example, all you need to do to soften the compiler's stance is create a function such as ... # void autoexist(inout S[int] x, int y) # { # if (y in x == null) x[y] = *new S; # } and then prior to you accessing a potentially missing entry ... # lookup.autoexist(key); However, I can see that there have been good solid arguments for both types of AA behavior. In some circumstances an AA ought to raise an error if one attempts to access a non-existant entry and in other circumstances an AA ought to create a default entry. As Mr. Bright has experience in doing both for D, it would not be a big stretch for a small syntax change so that one could identify, at AA declaration time, which of the two behaviors the AA will perform. I would recommend that a new token be introduced to mark those AA variables that automatically create a default entry when a non-existing entry is accessed. For indicicative purposes ... S[int] auto lookup; // Declare an auto-create AA S[int] masterlist; // Declare a non-auto-create AA
Jul 12 2005