May 03, 2005
Derek Parnell wrote:

>>I liked the old double-lookup in operator better, myself.
>>(i.e. the one that returned a "bit" value, not a pointer)
> 
> I must admit that I never use the pointer returned by 'in' except to see if
> it is null or not. I seem to always use this idiom ...
> 
>    if (X in Y)
>        Z = Y[X];
>    else
>        Z = whatever;
> 
> and
> 
>    if (X in Y != null)
>       Y[X] = value;
> 
> Am I doing this the hard way? Am I over cautious?

No, I use the very same approach myself...


It just "feels" better to me, than this:

if ((pZ = X in Y) != null)
    Z = *pZ;

Even if it causes two lookups to happen ?


But everyone seems to hate the short form of:
Z = Y[X]; // where Z gets the .init if missing

Currently unusable in D, since it will create
a brand new X key - if it was missing before...

--anders
May 03, 2005
On Tue, 03 May 2005 11:56:18 +0200, Anders F Björklund wrote:


[snip]

> But everyone seems to hate the short form of:
> Z = Y[X]; // where Z gets the .init if missing
> 
> Currently unusable in D, since it will create
> a brand new X key - if it was missing before...

Exactly! If X isn't there its because of a very good reason (or a bug), otherwise I want to know that it's not there. Sneaking it in seems just plain stupid.

-- 
Derek Parnell
Melbourne, Australia
http://www.dsource.org/projects/build v2.05 is now available. 02/May/2005
3/May/2005 8:16:51 PM
May 03, 2005
Derek Parnell wrote:

> Exactly! If X isn't there its because of a very good reason (or a bug),
> otherwise I want to know that it's not there. Sneaking it in seems just
> plain stupid.

I thought it was similar to using an "uninitialized" X ?
(which is always initialized to a default .init value)

But like I said, most everyone else disagreed :-)

And throwing an exception or creating a new key doesn't
matter to me, I guess either of is "logical" to someone.

I'll just use the "Z = (X in Y) ? Y[X] : whatever;" form.

--anders
May 03, 2005
In article <wtnge4kxj6zb$.hraa9mj820mh.dlg@40tude.net>, Derek Parnell says...
>
>On Tue, 03 May 2005 11:56:18 +0200, Anders F Björklund wrote:
>
>
>[snip]
> 
>> But everyone seems to hate the short form of:
>> Z = Y[X]; // where Z gets the .init if missing
>> 
>> Currently unusable in D, since it will create
>> a brand new X key - if it was missing before...
>
>Exactly! If X isn't there its because of a very good reason (or a bug), otherwise I want to know that it's not there. Sneaking it in seems just plain stupid.

Yes, it is.
Now, you need to explain it to Walter.

Ant


May 04, 2005
In article <d57hs1$1jnv$1@digitaldaemon.com>, =?ISO-8859-1?Q?Anders_F_Bj=F6rklund?= says...
>
>Derek Parnell wrote:
>
>>>I liked the old double-lookup in operator better, myself. (i.e. the one that returned a "bit" value, not a pointer)
>> 
>> I must admit that I never use the pointer returned by 'in' except to see if it is null or not. I seem to always use this idiom ...
>> 
>>    if (X in Y)
>>        Z = Y[X];
>>    else
>>        Z = whatever;
>> 
>> and
>> 
>>    if (X in Y != null)
>>       Y[X] = value;
>> 
>> Am I doing this the hard way? Am I over cautious?
>
>No, I use the very same approach myself...
>
>
>It just "feels" better to me, than this:
>
>if ((pZ = X in Y) != null)
>     Z = *pZ;
>
>Even if it causes two lookups to happen ?

How about this:

: if (X in Y)
:     Z = *(X in Y);

If the compiler is clever enough, it should be able to optimize to one lookup, as long as it can assume the value wasn't added between the "in"s.

--> So... is it, or could it be, smart enough to do one lookup?

P.S. This is another advantage of built-in containers if it can do so, because a C++ compiler would (probably) never be able to make assumptions like "the value of operator[] can be cached" but D probably CAN.

In C++, the compiler (in general) has no idea whether it is working with a container or something that *requires* two checks, like a spinlock implementation.  I think it has to assume the "X in Y" operations are potentially seperate stages and check twice;  a current or future version of D could avoid that by checking once.

Kevin



May 04, 2005
Kevin Bealer wrote:

>>It just "feels" better to me, than this:
>>
>>if ((pZ = X in Y) != null)
>>    Z = *pZ;
>>
>>Even if it causes two lookups to happen ?
> 
> How about this:
> 
> : if (X in Y)
> :     Z = *(X in Y);
> 
> If the compiler is clever enough, it should be able to optimize to one lookup,
> as long as it can assume the value wasn't added between the "in"s.
> 
> --> So... is it, or could it be, smart enough to do one lookup?

You mean that a clever compiler could make
the second example into the first example :-)

That's great, but it is still rather ugly...

Like in the first line, "in" is a pretend-boolean
but in the second it's one of those hated pointers ?

Z = Y[X]; // this is still the shortest/cleanest.

But I think we hijacked this thread long enough now.

--anders
May 05, 2005
In article <d59u9i$1dj4$1@digitaldaemon.com>
..
>You mean that a clever compiler could make
>the second example into the first example :-)
>
>That's great, but it is still rather ugly...
>
>Like in the first line, "in" is a pretend-boolean
>but in the second it's one of those hated pointers ?
>
>Z = Y[X]; // this is still the shortest/cleanest.
>
>But I think we hijacked this thread long enough now.
>
>--anders

Okay, granted on both points -- thus, I'll change the subject of the thread.

I think this kind of relates to the opIndex() vs. opIndexAssign() dilemma.

The default AA returns (effectively) something like a C++ "T&" type.  If you
design a class, you have to write opIndex() (a T return type) or opIndexAssign()
(which essentially takes T as in "in" parameter).

If the default AA followed what classes do, it could return T for the opIndex()
method, or always-add like C++ and the current AA for opIndexAssign().

The question then, is whether to:

1. Return a mutable reference aka "lvalue", or in D terminology "inout" type, which is assignable, ++able, or pass-able to a function's "inout" parameter.

2. Seperate opIndex() and opIndexAssign(), so that opIndex() can only fetch an
element from the class, but opIndexAssign() can write into the class.

Currently, builtin AA's do #1, which forces the builtin AA type to do the C++ like "create whenever we see X[key]".  And current user constructed classes do #2 instead; they allow you to not create on opIndex, instead returning some kind of null (T.init maybe), but on opIndexAssign() they can still create an object.

To quote Humpty Dumpty from AIW: "The question is, who is to be the master?". In other words, does the client code have the power, or the class?

If the client code has the power (#1), it can modify an lvalue in way at all,
using ++ or foo(inout T), and the container needs to return an rvalue (somehow)
as the builtin AA does.

If the container class has the power (#2), as current built-in classes do, then the client is restricted to two operations: X[i] = foo, or foo = X[i].

---

I think the ideal thing, semantically, would be for D to somehow convert X[i]++ to:

X[i] = X[i]++;

And, for:

foo(inout T);
foo(X[i]);

.. to effectively be the same as:

T z = X[i];
foo(z);
X[i] = z;

---> Thus, we can keep opIndex() and opIndexAssign().  If opIndex cannot find
the object, it can return T.init, which converts the above statement to:

T z;
foo(z);
X[i] = z;

The compiler would presumably be able to produce that last snippet every time if
it knows that foo(z) is actually foo(out T).  This leads to:

foo(in x);    foo(X[i]);   // only one lookup, opIndex();
foo(out x);   foo(X[i]);   // only one lookup, opIndexAssign();
foo(inout x); foo(X[i]);   // two lookups (opIndex(); foo(); opIndexAssign();)

The last case is the issue.  It could use the design I show here; potentially there are two lookups unless the optimizer can remove one of them.

Or it could use Ben Hinkle's suggestion in the opIndexMutable() thread, that the indexing operator return T*, which the compiler would dereference as shown:

foo(inout x); foo(X[i]);  ---->   foo(* X.opIndexMutable(i));

Kevin


May 05, 2005
> Or it could use Ben Hinkle's suggestion in the opIndexMutable() thread,
> that the
> indexing operator return T*, which the compiler would dereference as
> shown:
>
> foo(inout x); foo(X[i]);  ---->   foo(* X.opIndexMutable(i));
>
> Kevin

You're being too generous - the opIndexMutable was your suggestion :-) My suggestion was the use of a flag at the call site in front of X[i] to keep C's "bottom-up" type resolution.


May 06, 2005
In article <d5d8ei$16eo$1@digitaldaemon.com>, Ben Hinkle says...
>
>> Or it could use Ben Hinkle's suggestion in the opIndexMutable() thread,
>> that the
>> indexing operator return T*, which the compiler would dereference as
>> shown:
>>
>> foo(inout x); foo(X[i]);  ---->   foo(* X.opIndexMutable(i));
>>
>> Kevin
>
>You're being too generous - the opIndexMutable was your suggestion :-) My suggestion was the use of a flag at the call site in front of X[i] to keep C's "bottom-up" type resolution.

I was thinking that automatically using the "*" was yours, but I forgot to look it up.  Am I guilty of brain-child abandonment?

Kevin



1 2 3
Next ›   Last »