www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - AA default value?

reply "Janice Caron" <caron800 googlemail.com> writes:
The big problem for me with associative arrays is that I often have to
write ugly code like:

    auto p = key in aa;
    if (p)
    {
    	auto val = *p;
    }
    else
    {
        /* whatever */
    }

instead of merely

    auto val = aa[key];

In order to avoid superfluous lookups, the code that I actually end up
writing doesn't use the square brackets at all, which kind of defeats
the object of the built-in array-like syntax. What would seriously
make for nicely readable code would be if the behavior of aa[key]
could be changed. Currently, aa[key] throws an exception (in Debug
mode) or falls over (in Release mode) if key does not exist. Wouldn't
it be nicer if, instead, it simply returned

	typeof(aa[key]).init

if the key was not present in the array? Or perhaps an even more
exotic syntax like

    auto val = aa[key; defaultValue];

might work.

(None of this would preclude people from using "in" if they explicitly
wanted to check).
Jan 25 2008
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Janice Caron" <caron800 googlemail.com> wrote in message 
news:mailman.24.1201263099.5260.digitalmars-d puremagic.com...

 Wouldn't
 it be nicer if, instead, it simply returned

 typeof(aa[key]).init

 if the key was not present in the array?

What's funny is that a while ago ((long) before 1.0), if you looked up a key in an AA and the key didn't exist, it'd be added with a value of typeof(aa[key]).init. So int[int] aa; writefln(aa[5]); would give "0" as the output. This was to mirror the behavior of C++'s std::map but many people opposed it, so now we have what we have now.. The issue is that you can't cover all the bases with a single type. Some people want the auto-insertion, some don't. Some want it to disallow updates, i.e. allow a key-value pair to be inserted but never modified. Some want the opposite, where you have to explicitly use an insert method to insert the values, but once they're in, they can be modified as much as you like. At least the nice thing about the AAs is that they're simple to build custom containers on top of them, so most/all of these things can be implemented as structs wrapping them. The downside is that with the current set of operator overloads and features, you can never make a custom type that can replace the builtin one in all cases.
Jan 25 2008
parent reply Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Jarrett Billingsley wrote:
 "Janice Caron" <caron800 googlemail.com> wrote in message 
 news:mailman.24.1201263099.5260.digitalmars-d puremagic.com...
 
 Wouldn't
 it be nicer if, instead, it simply returned

 typeof(aa[key]).init

 if the key was not present in the array?

What's funny is that a while ago ((long) before 1.0), if you looked up a key in an AA and the key didn't exist, it'd be added with a value of typeof(aa[key]).init. So int[int] aa; writefln(aa[5]); would give "0" as the output. This was to mirror the behavior of C++'s std::map but many people opposed it, so now we have what we have now..

I believe the implemented solution to the old problem overshoot the best solution, which would have been to make a rvalue expression aa[5] return 0, but *without* inserting 5 into the aa. I have for ages had the following two simple utility functions in most code I write that uses AAs: T get(T,U)(T[U] aa, U key) { T* ptr = key in aa; if (ptr) return *ptr; return T.init; } T get(T,U,int dummy=1)(T[U] aa, U key, lazy T defaultValue) { T* ptr = key in aa; if (ptr) return *ptr; return defaultValue; } and almost always use aa.get(5), instead of aa[5].
 The issue is that you can't cover all the bases with a single type.  Some 
 people want the auto-insertion, some don't.  Some want it to disallow 
 updates, i.e. allow a key-value pair to be inserted but never modified. 
 Some want the opposite, where you have to explicitly use an insert method to 
 insert the values, but once they're in, they can be modified as much as you 
 like.
 
 At least the nice thing about the AAs is that they're simple to build custom 
 containers on top of them, so most/all of these things can be implemented as 
 structs wrapping them.  The downside is that with the current set of 
 operator overloads and features, you can never make a custom type that can 
 replace the builtin one in all cases. 

The biggest problem with custom containers is the lack of a reference return type. -- Oskar
Jan 25 2008
parent reply Sean Kelly <sean f4.ca> writes:
Oskar Linde wrote:
 Jarrett Billingsley wrote:
 "Janice Caron" <caron800 googlemail.com> wrote in message
 news:mailman.24.1201263099.5260.digitalmars-d puremagic.com...

 Wouldn't
 it be nicer if, instead, it simply returned

 typeof(aa[key]).init

 if the key was not present in the array?

What's funny is that a while ago ((long) before 1.0), if you looked up a key in an AA and the key didn't exist, it'd be added with a value of typeof(aa[key]).init. So int[int] aa; writefln(aa[5]); would give "0" as the output. This was to mirror the behavior of C++'s std::map but many people opposed it, so now we have what we have now..

I believe the implemented solution to the old problem overshoot the best solution, which would have been to make a rvalue expression aa[5] return 0, but *without* inserting 5 into the aa.

Agreed. And I think this was brought up at the time, but it wasn't implemented for some reason. Sean
Jan 25 2008
parent reply Jason House <jason.james.house gmail.com> writes:
Sean Kelly Wrote:

 Oskar Linde wrote:
 I believe the implemented solution to the old problem overshoot the best
 solution, which would have been to make a rvalue expression aa[5] return
 0, but *without* inserting 5 into the aa.

Agreed. And I think this was brought up at the time, but it wasn't implemented for some reason.

That solution is assuming a specific answer to the question of "should indexing a non-existent entry be an error?". Sometimes, I write code that assumes stuff will be pre-existing in an AA / STL map. When this is the case, I prefer the D behavior. When code works better with defaults, it's annoying. I'd really prefer to have a choice. Additionally, AA's of classes that return null for unused indexes isn't particularly helpful. Returning a non-duped default object is usually worse than returning null. To me, there seems to be enough variation in what people would want that some kind of policy (template paramter, included function object, etc...) would be better.
Jan 25 2008
parent Jason House <jason.james.house gmail.com> writes:
Janice Caron Wrote:

 On 1/25/08, Jason House <jason.james.house gmail.com> wrote:
 I'd really prefer to have a choice.

Yeah! I'll second that. Not sure how it would work though. Maybe something like: int[int] aa; auto x = aa[5]; // run-time error aa.default = 0; auto y = aa[5]; // y = 0;
 Additionally, AA's of classes that return null for unused indexes isn't
particularly helpful.  Returning a non-duped default object is usually worse
than returning null.  To me, there seems to be enough variation in what people
would want that some kind of policy (template paramter, included function
object, etc...) would be better.

Sounds good to me. Maybe the .default property could be lazy (so it could call new when needed)?

I'd be content with (lazy) syntax above. Two gotchas: * The default keyword (used in switches) may block that property * New AA's are null and property access would fail. Of course, I'd like to see that changed anyway.
Jan 25 2008
prev sibling next sibling parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
Janice Caron wrote:
 Wouldn't
 it be nicer if, instead, it simply returned
 
 	typeof(aa[key]).init
 
 if the key was not present in the array?

But then you can't tell wheter the key was present and the value was init, or it was not present at all.
Jan 25 2008
parent reply Leandro Lucarella <llucax gmail.com> writes:
Janice Caron, el 25 de enero a las 14:58 me escribiste:
 On 1/25/08, Ary Borenszweig <ary esperanto.org.ar> wrote:
 Janice Caron wrote:
 Wouldn't
 it be nicer if, instead, it simply returned

       typeof(aa[key]).init

 if the key was not present in the array?

But then you can't tell wheter the key was present and the value was init, or it was not present at all.

If knowing that is important, you must use "in", as you always have done. I wasn't for one moment suggesting deprecating "in". However, there are plenty of circumstances where the distinction is unimportant. Another approach would be to specify the default at the time you make the call (instead of relying on init). Of course, for that you'd need a new syntax - e.g. aa[key; defaultValue] or aa[key ? defaultValue] or aa[key else defaultValue] etc.

aa.get(key, defaultvalue) (a la python)? I think your proposed syntax, well, besides being a new syntax =P, it's not clear because is not the key which will be replaced for a default value, the default value "else" clause should be outside the [] (if a new syntax is wanted), which I think is hard to do. -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ ---------------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------------- HOMBRE DESNUDO AMENAZA A LOS VECINOS CON UNA "KATANA" DESDE SU BALCON -- Crónica TV
Jan 25 2008
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Janice Caron wrote:
 On 1/25/08, Janice Caron <caron800 googlemail.com> wrote:
     ((p = (key in aa)) is null) ? *p : defaultValue)

Spot the deliberate (ahem) mistake! Hint - there's a missing exclamation mark somewhere. :-) Seriously, really I just want aa[key] to return some default value. Whether that's value.init, or some settable property within AAs, or if it could somehow be expressed at the call site - any or all of those options would be fine.

Oskar gave you a solution. T get(T,U)(T[U] aa, U key) { T* ptr = key in aa; if (ptr) return *ptr; return T.init; } T get(T,U,int dummy=1)(T[U] aa, U key, lazy T defaultValue) { T* ptr = key in aa; if (ptr) return *ptr; return defaultValue; } Which looks exactly like a D translation of Python's get method for dicts. Python also has setdefault, which does the same as get() but also sets the value to the specified default if it didn't exist before. I'm not wild about that name "setdefault" but anyway having the method in the std lib is good. T setdefault(T,U)(T[U] aa, U key) { T* ptr = key in aa; if (ptr) return *ptr; aa[key] = T.init; return T.init; } T setdefault(T,U,int dummy=1)(T[U] aa, U key, lazy T defaultValue) { T* ptr = key in aa; if (ptr) return *ptr; aa[key] = defaultValue return aa[key]; } --bb
Jan 25 2008
prev sibling next sibling parent Jason House <jason.james.house gmail.com> writes:
Janice Caron Wrote:

 The big problem for me with associative arrays is that I often have to
 write ugly code like:
 [snip]
 if the key was not present in the array? Or perhaps an even more
 exotic syntax like
 
     auto val = aa[key; defaultValue];
 
 might work.

If the behavior is defined at the location of the index call, I'd be very happy with adding exotic syntax similar to what you propose. Another candidate would be to make behavior with invalid indexing a policy that's included in the AA definition.
Jan 25 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 1/25/08, Ary Borenszweig <ary esperanto.org.ar> wrote:
 Janice Caron wrote:
 Wouldn't
 it be nicer if, instead, it simply returned

       typeof(aa[key]).init

 if the key was not present in the array?

But then you can't tell wheter the key was present and the value was init, or it was not present at all.

If knowing that is important, you must use "in", as you always have done. I wasn't for one moment suggesting deprecating "in". However, there are plenty of circumstances where the distinction is unimportant. Another approach would be to specify the default at the time you make the call (instead of relying on init). Of course, for that you'd need a new syntax - e.g. aa[key; defaultValue] or aa[key ? defaultValue] or aa[key else defaultValue] etc. But yeah - under the first idea, if you really need to tell whether the key was present and the value was init, you'd just do key in init i.e. no change. Existing code still does the job.
Jan 25 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 1/25/08, Janice Caron <caron800 googlemail.com> wrote:
 But yeah - under the first idea, if you really need to tell whether
 the key was present and the value was init, you'd just do

     key in init

 i.e. no change. Existing code still does the job.

Typo. I meant key in aa
Jan 25 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 1/25/08, Leandro Lucarella <llucax gmail.com> wrote:
 I think your proposed syntax ... it's
 not clear

I'm not proposing a syntax, I'm proposing an idea. I don't care about the syntax. At least, so long as it's not ((p = (key in aa)) is null) ? *p : defaultValue) :-)
Jan 25 2008
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 1/25/08, Janice Caron <caron800 googlemail.com> wrote:
     ((p = (key in aa)) is null) ? *p : defaultValue)

Spot the deliberate (ahem) mistake! Hint - there's a missing exclamation mark somewhere. :-) Seriously, really I just want aa[key] to return some default value. Whether that's value.init, or some settable property within AAs, or if it could somehow be expressed at the call site - any or all of those options would be fine.
Jan 25 2008
prev sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 1/25/08, Jason House <jason.james.house gmail.com> wrote:
 I'd really prefer to have a choice.

Yeah! I'll second that. Not sure how it would work though. Maybe something like: int[int] aa; auto x = aa[5]; // run-time error aa.default = 0; auto y = aa[5]; // y = 0;
 Additionally, AA's of classes that return null for unused indexes isn't
particularly helpful.  Returning a non-duped default object is usually worse
than returning null.  To me, there seems to be enough variation in what people
would want that some kind of policy (template paramter, included function
object, etc...) would be better.

Sounds good to me. Maybe the .default property could be lazy (so it could call new when needed)?
Jan 25 2008