From: DaveOz on
Hello,
I encountered a behavior of dict exists which I thought seemed
unexpected. I'm relatively new to TCL so apologies if I'm missing
something obvious or going over old ground
..
Can anyone confirm that this is indeed TCL working as designed?

I have a dictionary which is sometimes nested, sometimes not. So I
need to test whether a value in the nested dict is there before
attempting to do a dict get.

eg.

> set dict [dict create a [dict create c 3] b 2]
a {c 3} b 2
> dict exists $dict a c
1

All good.. but when the nested dict is not there, I get an error doing
the same test:

> dict set dict a 1
a 1 b 2
> dict exists $dict a c
missing value to go with key

I would have liked this just to return a 0 instead of an error, but as
it stands, to check if the nested value exists and avoid the
possibility of an error I need to something along the lines of:
> if { [dict exists $dict a] && [llength [dict get $dict a] > 1] && [dict exists $dict a c] } {
.....
}

Seems a bit cumbersome.. how do most folk get round this?

> info patchlevel
8.5.8
From: MartinLemburg on
Hi Dave,

"dict exists" tries to convert the data to test the existence of a key
on into a dictionary!

So ...

dict exists [dict create a 1 b 2] a c

.... will first try to do ...

dict get {a 1 b 2} a c

.... which is the same like ...

dict get [dict get {a 1 b 2} a] c

.... which is the same like ...

dict get 1 c

.... which causes the error:

"missing value to go with key"

"dict exists" only works on real dictionaries or lists with an even
count of elements.
So it is not the right thing to test for a nested dictionary!

Something like the following code could be used to test, if data is
usable as dictionary:

proc stringIsDict {string} {
# loosely tested an empty string is a dictionary
#
if {$string eq ""} {
return 1;
}

# parsable as list, than parsable as dict
#
if {![string is list $string]} {
return -code ok -errorcode [list EINVAL "not parsable as
list or dict"] 0;
}

# an even count of elements used as pairs of key and value
#
if {[llength $string] % 2 != 0} {
return -code ok -errorcode [list EINVAL "no even count of
elements to represent key-value-pairs"] 0;
}

# unique keys
#
set keys [list];

foreach {key value} $string {
if {$key in $keys} {
return -code ok -errorcode [list EINVAL "duplicate key
\"$key\" found"] 0;
}

lappend keys $key;
}

return 1;
}

After applying "stringIsDict" you could access the data/string as
dictionary.

Best regards,

Martin
On 31 Mai, 17:21, DaveOz <daveo...(a)gmail.com> wrote:
> Hello,
> I encountered a behavior of dict exists which I thought seemed
> unexpected. I'm relatively new to TCL so apologies if I'm missing
> something obvious or going over old ground
> .
> Can anyone confirm that this is indeed TCL working as designed?
>
> I have a dictionary which is sometimes nested, sometimes not. So I
> need to test whether a value in the nested dict is there before
> attempting to do a dict get.
>
> eg.
>
> > set dict [dict create a [dict create c 3] b 2]
> a {c 3} b 2
> > dict exists $dict a c
>
> 1
>
> All good.. but when the nested dict is not there, I get an error doing
> the same test:
>
> > dict set dict a 1
> a 1 b 2
> > dict exists $dict a c
>
> missing value to go with key
>
> I would have liked this just to return a 0 instead of an error, but as
> it stands, to check if the nested value exists and avoid the
> possibility of an error I need to something along the lines of:
>
> > if { [dict exists $dict a] && [llength [dict get $dict a] > 1] && [dict exists $dict a c] } {
> ....
> }
>
> Seems a bit cumbersome.. how do most folk get round this?
>
> > info patchlevel
>
> 8.5.8

From: DaveOz on
Thanks very much for your help Martin and the code snippet.
I see why dict exists acts the way it does from the example you gave.

I guess I was making assumptions from the description of it:

"[dict exists] returns a boolean value indicating whether the given
key (or path of keys through a set of nested dictionaries) exists in
the given dictionary value. This returns a true value exactly when
dict get on that path will succeed. "

When it says it will return a true value exactly when dict get
succeeds, I made the mistake of assuming the converse would also be
true: i.e. it will return false exactly when dict get would fail for
the path in question. Hypothetically, would that not seem like more
consistent behavior?
From: MartinLemburg on
> Hypothetically, would that not seem like more consistent behavior?

Yes!

I would even assume, that "dict exists" on an invalid dict should
return 0 or an appropriate error message, which would not be the same,
than that from "dict get".

Best regards,

Martin

On 1 Jun., 11:01, DaveOz <daveo...(a)gmail.com> wrote:
> Thanks very much for your help Martin and the code snippet.
> I see why dict exists acts the way it does from the example you gave.
>
> I guess I was making assumptions from the description of it:
>
> "[dict exists] returns a boolean value indicating whether the given
> key (or path of keys through a set of nested dictionaries) exists in
> the given dictionary value. This returns a true value exactly when
> dict get on that path will succeed. "
>
> When it says it will return a true value exactly when dict get
> succeeds, I made the mistake of assuming the converse would also be
> true: i.e. it will return false exactly when dict get would fail for
> the path in question.  Hypothetically, would that not seem like more
> consistent behavior?