in reply to Re^3: Using exists to check element in array
in thread Using exists to check element in array

To misquote Bill Clinton:

"It depends if your meaning of exist exists."

;)

Your table shows exactly a consistent behavior to exists when used with hashes. Unset values just don't exist.

$a[0] was never set, hence it doesn't exist.

I wasn't sure what you and sectokia meant with null-pointer, apparently it's the C-value of unset "gap" values¹ in the underlying C-array.

But those exist only on the C level, not logically in Perl.

> but there might be surprise cases (for (@a)? for ((), @a)? f(@a)?)

I don't understand, probably above my expertise.

Cheers Rolf
(addicted to the Perl Programming Language :)
see Wikisyntax for the Monastery

¹) with an index smaller than the maximal index of a set value

Replies are listed 'Best First'.
Re^5: Using exists to check element in array
by ikegami (Patriarch) on Jan 30, 2024 at 21:18 UTC

    Your table shows exactly a consistent behavior to exists when used with hashes.

    No, it's not consistent with hashes.

    $ perl -e' my %h = ( a => 4, c => 6 ); printf( "exists %s return false for elements found in a hash.\n", ( grep { !exists( $h{$_} ) } keys %h ) ? "can" : "didn\x27t" ); my @a; $a[0] = 4; $a[2] = 6; printf( "exists %s return false for elements found in an array.\n", ( grep { !exists( $a[$_] ) } keys @a ) ? "can" : "didn\x27t" ); ' exists didn't return false for elements found in a hash. exists can return false for elements found in an array.

    The very existence of this inconsistency is reason enough for a warning.

    $a[0] was never set, hence it doesn't exist.

    No, that means it wasn't set. Setting or not setting has nothing to do whether an element is found in an array or not, and with what exists returns.

    exists can obviously return false for elements that were never set, but it can also return true for such elements as the following demonstrates:

    $ perl -Mv5.14 -e'my @a; \$a[0]; say exists( $a[0] ) || 0;' 1

    Your implication that exists checks whether an element was set or not is incorrect. As I already mentioned, exists checks if an element is within the array and if it isn't NULL.

    I don't understand, probably above my expertise.

    These are things that might unintentionally change replace the NULL with a (pointer to a) scalar. I didn't test.

    Upd: I just did and they don't. But \$a[0] does, as shown above.

      Time and again I see you changing your posts in retrospect without marking updates.

      For the sake of a decent discussion, please be consistent.

      Now, I'll split my reply into multiple posts, so lets target my first argument:

      Firstly:

      your original example was:

      my @a; $a[1] = undef; $a[2] = 0; $a[3] = 7;

      and I replied

      > > Your table shows exactly a consistent behavior to exists when used with hashes. Unset values just don't exist.

      and here we go

      use v5.12; use warnings; my (@x,%x); my $i=1; for my $v (undef,0,7) { $x[$i]=$x{$i}=$v; $i++; }
      --- Index exists? $i @x %x 0 1 1 1 2 1 1 3 1 1 4

      QED!

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

        Time and again I see you changing your posts in retrospect without marking updates

        Perhaps PM should enforce some text to explain node edits...

        Of course, it doesn't stop gibberish but the presence of gibberish will demonstrate that a change was made and imply that the poster didn't want people to know why they edited their post.

        No idea what you're trying to say. You simply wrote a program that prints a subset of my table. Well, you showed the output of such a program, but didn't actually provide it. Nothing here contradicts what I said, but you claim it does?

      Secondly

      > ( grep { !exists( $a[$_] ) } keys @a ) ? "can" : "didn\x27t" );

      Looks like keys @array was added to Perl in 5.12

      And it's obviously inconsistent to delete and exists .

      > The very existence of this inconsistency is reason enough for a warning.

      But it's even worse!!!

      keys is inconsistent to the behavior in hashes, even without using delete and exists and ever seeing any warnings !!!

      my (@y,%y); $y[3]=$y{3}=1; say "keys \%y: ", join ",",keys %y; say "keys \@y: ", join ",",keys @y;

      -->

      keys %y: 3 keys @y: 0,1,2,3

      A consistent implementation of keys ARRAY should check with exists first before returning an index.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

        And it's obviously inconsistent to delete and exists

        That's what I've been saying. exists isn't consistent. That's why there's a warning. And you're right, delete isn't either. We've proved this so many times already!

        keys is inconsistent to the behavior in hashes

        Next you'll say that scalar(@a) and scalar(%h) are inconsistent!

        Perl doesn't have sparse arrays. If $y[3] is in the array, so is $y[2].

        keys returns the elements found the structure for both arrays and hashes. keys is consistent for both arrays and hashes.

        Only exists and delete are inconsistent, as repeatedly demonstrated.

      Thirdly:

      > exists can obviously return false for elements that were never set, but it can also return true for such elements as the following demonstrates:

      >

      $ perl -Mv5.14 -e'my @a; \$a[0]; say exists( $a[0] ) || 0;' 1

      now that's extra weird, because introspecting with Devel::Peek reveals that the implementation is (falsely) differentiating between different kinds of 0s and NULLs.

      I'm no C nor even XS hacker, but this looks like exists's understanding of NULL is just to narrow. ¹

      I can't see why this shouldn't be fixable.

      use Devel::Peek; my @z; $a = \$z[1]; say "\$z[$_] exists: ", exists( $z[$_] ) || 0 for 0..2; say '---Dump @z'; Dump @z; say '---Dump $z[0]'; Dump $z[0]; say '---Dump $z[1]'; Dump $z[1];

      comments ### added
      $z[0] exists: 0 $z[1] exists: 1 $z[2] exists: 0 ---Dump @z SV = PVAV(0x2770318) at 0x27a4930 REFCNT = 1 FLAGS = () ARRAY = 0x2787080 FILL = 1 MAX = 3 FLAGS = (REAL) Elt No. 0 SV = 0 ### 0 Elt No. 1 SV = NULL(0x0) at 0x2783d70 REFCNT = 2 FLAGS = () ---Dump $z[0] SV = NULL(0x0) at 0x7a91d0 ### NULL (WTF?) REFCNT = 2147483641 FLAGS = (READONLY,PROTECT) ---Dump $z[1] SV = NULL(0x0) at 0x2783d70 REFCNT = 2 FLAGS = ()

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

      ¹) a null understanding ;-)

        now that's extra weird, because introspecting with Devel::Peek reveals that the implementation is (falsely) differentiating between different kinds of 0s and NULLs.

        The "other kind of 0 or NULL" is an undefined scalar.

        exists differentiates between what Dump shows as SV = 0 (underlying NULL in the array) and everything else (underlying non-NULL in the array).

        Say you have this variable:

        my @a; $a[1] = undef; $a[2] = 123;

        Devel::Peek shows

        SV = PVAV(0x**6adf0) at 0x**99148 REFCNT = 1 FLAGS = () ARRAY = 0x**8b9c0 FILL = 2 MAX = 3 FLAGS = (REAL) Elt No. 0 SV = 0 Elt No. 1 SV = NULL(0x0) at 0x**69538 REFCNT = 1 FLAGS = () Elt No. 2 SV = IV(0x**696f0) at 0x**69700 REFCNT = 1 FLAGS = (IOK,pIOK) IV = 123

        (Replaced the common start of the addresses with ** to help with readability.)

        The underlying array buffer of @a looks like this:

        ARRAY @ 0x**8b9c0 +-------------+ Head 0 | NULL | @ 0x**69538 +-------------+ +-------------+ 1 | 0x**69538 --------->| NULL | Body +-------------+ +-------------+ 2 | 0x**69700 ------+ | 1 | REFCNT +-------------+ | +-------------+ 3 | | | | ... | FLAGS +-------------+ | +-------------+ | | SVt_NULL | TYPE | +-------------+ | | | | +-------------+ | | Head Body | @ 0x**69700 @ 0x**696f0 | +-------------+ +-----------+ +-->| 0x**696f0 --------->| ... +-------------+ | 1 | REFCNT +-------------+ | SVf_IOK|... | FLAGS +-------------+ | SVt_IV | TYPE +-------------+ | ... | +-------------+

        (Technically, the body of an SVt_IV scalar is part of the same memory block as its head, but illustrating that would just complicated things needlessly.)

        • SV = 0 indicates a NULL pointer in the array.
        • SV = followed by anything else indicates there's a non-NULL pointer in the array.

        What follows SV = is the type of scalar.

        • SV = NULL indicates a scalar of type NULL (SvTYPE(sv) == SVt_NULL). The only value a scalar of this type can hold is undef. my $x; creates such a scalar.
        • SV = IV indicates a scalar of type IV (SvTYPE(sv) == SVt_IV). It can hold one of the following: undef, a signed integer, an unsigned integer or a reference.

        The second number in SV = TYPE(0xBBB) at 0xHHH is the address of the scalar's head (i.e. of the scalar itself), and the first number is the address of the scalar's body. Scalars of type NULL have no body. As such, the pointer to the scalar's body in such scalars will be NULL (0x0).

        In hindsight, this existence-by-reference trick works for hashes too. Hence it's at least consistent.

        The big question is now, if and what rationale there this.

        Probably not really a problem at all.

        DB<1> %x=(a=>1) DB<2> $a= \$x{b} DB<3> p exists $x{b} 1 DB<4> p exists $x{a} 1 DB<5> p exists $x{c} DB<6>

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        see Wikisyntax for the Monastery