Bug - Not A Bug Referencing a non-existent map key in ASH should produce an error

Catch-22

Active member
Code:
> ash location [string] my_map; (my_map["Non-existent key"]);

Returned: none
nocombats => false
zone =>
parent =>
parentdesc =>
bounty => none

I would think this should produce an error because we're not referencing a key with any assigned values, but apparently it's still valid code.

When I was looking into this bug, I noticed that even if you remove the "Degrassi Knoll" key from AdventureAdvisors kingdom[] map you can still reference a "valid" location by doing kingdom["Degrassi Knoll"].loc.

It's not just locations that are affected, it's maps in general.

Code:
> ash float [int] my_map; (my_map[1]);

Returned: 0.0
 

Catch-22

Active member
Maps generate dynamically when you call them, I do think there is a base reason behind that.

This should only be true if the key has an associated value pair, see below:

Code:
float [int] my_map;
print("Value: " + my_map[1]);
print("Count: " + my_map.count());
my_map[1] = 0.0;
print("Value: " + my_map[1]);
print("Count: " + my_map.count());

Output:
Value: 0.0
Count: 0
Value: 0.0
Count: 1
 

xKiv

Active member
Don't forget to keep stuff like
Code:
my_map[1][2]=3;
working (when my_map contains 1 == false) (you can have maps of maps that way, right?).

I think this might be a bit difficult to do *just right*.
People might even rely on "default" values for missing keys in their code. Like
Code:
my_map[x]+=1;
Do you really want to write several lines for this in every place where you are just increasing a counter?
Code:
if (my_map contains x) {
  my_map[x] += 1;
} else {
  my_map[x] = 0;
}
// ALT:
my_map[x] = my_map contans x ? my_map[x]+1 : 0; // yuck :( ... and I don't even know if this is valid ASH syntax
 

Veracity

Developer
Staff member
This is intended behavior.

You are welcome to use "contains" in front of every map reference to check if the key is there, if it is important that your program distinguish between "not present" and "default value".

Otherwise, you currently get the default value. You are proposing that we abort the script with a runtime error? No.
 

Catch-22

Active member
Don't forget to keep stuff like
Code:
my_map[1][2]=3;
working (when my_map contains 1 == false) (you can have maps of maps that way, right?).
In this case you're assigning 2 things, a map containing 3 and a map containing an aggregate (the map containing 3). This is perfectly fine, both keys have an associated value pair.

People might even rely on "default" values for missing keys in their code. Like
Code:
my_map[x]+=1;
Do you really want to write several lines for this in every place where you are just increasing a counter?

Eh? When you're doing my_map[x]+=1 it's fine because you're assigning my_map[x] the value of INIT_VALUE+1, perfectly valid.

You are proposing that we abort the script with a runtime error? No.

May I ask why? Referencing a key with no associated value pair seems like really strange behaviour to me. If someone is doing that in their code, to me it would seem like an accident and they should be notified that they're doing something silly.
 

StDoodle

Minion
And to me, it would seem like something I've done intentionally. But thanks for letting me know that following the documentation I was given for the language is silly, I'll try not to in the future.
 

Catch-22

Active member
And to me, it would seem like something I've done intentionally. But thanks for letting me know that following the documentation I was given for the language is silly, I'll try not to in the future.

You mean this documentation which makes no mention of such behaviour being intentional?

Perhaps you are referring to Veracity's original documentation on maps, which states "A map is indexed by one data type (the key) and associates that key with another (or the same) data type (the value)." which, to me, says if you have a key with no associated value, it goes against the very definition of a map.

Perhaps there's official documentation to the contrary which I don't know about.
 
Last edited:

nworbetan

Member
May I ask why? Referencing a key with no associated value pair seems like really strange behaviour to me. If someone is doing that in their code, to me it would seem like an accident and they should be notified that they're doing something silly.

Maps can be generated dynamically, and not causing an error when you reference a key that could have been generated, but wasn't, doesn't seem that silly to me.

I could say that doing an operation that uses a value without sanity checking the value first is strange. But you know, that statement would be kind of silly too, because sillyness and strangeness are both kind of contextually sensitive. Just like code.
 

Catch-22

Active member
Maps can be generated dynamically, and not causing an error when you reference a key that could have been generated, but wasn't, doesn't seem that silly to me.

You're right, the default behaviour of Java would be to return null because the value corresponding to the key reference is unknown. Given that KoLmafia doesn't support null (though it's still possible to generate null because of the way arithmetic currently works in ASH) and given that, previously, decisions have been made in which operations that would normally result in null would produce an error rather than arbitrary values such as 0, I would think that producing an error in this case would be consistent with intended ASH behaviour.
 

slyz

Developer
I'm not sure why the current behavior is to return the default value instead of producing a runtime error, and my layman instinct would be to consider both behaviors as more or less equivalent since in both cases the scripter needs to add a check to make sure he is handling a meaningful value.

Returning the default value does seem slightly more flexible, but adds a potential for mistakes in scripts (even for seasoned authors, apparently).

If there is a design decision or constraint behind this behavior other than the "flexibility / potential for unexpected behavior" trade-off, I would be happy for another lesson in coding practice.

On a historical note, referencing a non-existing key used to create that key in the map, associated with the default value. This behavior was partly changed in r8775. I say partly because of the following:
I changed it again to have the old behavior if the value you are requesting is mutable - a composite, a map or record. If you are fetching immutable objects - ints, strings, items, booleans, ... - fetching will not create the map entry.
So this script:
PHP:
record my_record
{
	location my_loc;
}

my_record [ string ] my_map;
print( "Value: " + my_map[ "Non-existent key" ].my_loc );
print( "Count: " + my_map.count() );
outputs:
Code:
Value: none
Count: 1

Meanwhile, if you are looking for a safer data structure, I think Arrays would be better suited.
 

Catch-22

Active member
since in both cases the scripter needs to add a check to make sure he is handling a meaningful value.

Not necessarily, I think in many cases it can be safe to assume that your map contains the key you are referencing. If there's a condition where the key might have been removed at some point, you should definitely check if it's contained in the map first.

If there is a design decision or constraint behind this behavior other than the "flexibility / potential for unexpected behavior" trade-off, I would be happy for another lesson in coding practice.

I am interested too, which is why my question to Veracity was simply "Why?" and I don't think I asked it in a rude way or anything.

On a historical note, referencing a non-existing key used to create that key in the map, associated with the default value. This behavior was partly changed in r8775. I say partly because of the following:

So this script:
PHP:
record my_record
{
	location my_loc;
}

my_record [ string ] my_map;
print( "Value: " + my_map[ "Non-existent key" ].my_loc );
print( "Count: " + my_map.count() );
outputs:
Code:
Value: none
Count: 1

Ah, interesting history note, thanks. To me that seems to be even more error prone... If you remove an existing key from your example map based on a condition of some sort, by accidentally referencing "Existing Key" later on, you're inadvertently causing that key to exist, later down the track you might do the right thing by checking if my_map contains "Existing Key" and it's going to return true (even though you had intended for the record to be removed from the map) but the value referenced by that key is going to be the default value, not the one you might've been expecting had the condition for key removal in the first place not been true.

Edit: Interesting post from Bale I noticed in the thread you linked to.

Once, long ago, I ran into an intractable bug that made absolutely no sense until I discovered what happens when I check a nonexistent map entry. Since then I've always been careful to check if the map contains the entry. While I've been as careful to avoid that problem as if I was tiptoeing around broken glass, I have never liked the fact that I always felt like I had to work around around the problem. I could deal with it, but it was icky. Even more icky is the fact that almost everyone needed to trip over the problem at least once before they learned to avoid it.

So it would seem some of the old-school ASH scriptwriters out there may have learned the hard way when it comes to assuming a map contains an entry, but in the case Bale refers to he seems to be talking about KoLmafia creating a map entry with the default value simply by referencing it (ie. the current behaviour for aggregate containing map keys). I think Bale would've learned the easy way had KoLmafia simply printed an error whenever this unintended map key referencing behaviour had occurred.
 
Last edited:
Top