david has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to understand why the following warning is give in perldoc.

"WARNING: Calling exists on array values is strongly discouraged. The notion of deleting or checking the existence of Perl array elements is not conceptually coherent, and can lead to surprising behavior."

I am no longer doing this having swapped to using a hash with numerical keys instead. In my situation the numerical keys ($elementIndex) are not contiguous i.e 0,3,7. Checking if an element exists using  if (exists $myArray[$elementIndex]) {... seemed to work as expected, so I am trying understand why this warning is given?

Replies are listed 'Best First'.
Re: Using exists to check element in array
by LanX (Saint) on Jan 28, 2024 at 09:22 UTC
    Because people tend to confuse defined and exists with array-elements. Especially when using delete on array-elements. ¹

    We had recently a discussion covering this, see Re^3: extracting a list element ( 'delete @array' explained) and connected thread.

    > In my situation the numerical keys ($elementIndex) are not contiguous

    Personally I think if you really know what you are doing, you should just silence this warning.

    NB: But sparse arrays with gaps of non-existing elements are normally more efficiently implemented with hashes.²

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

    updates

    ¹) and because Data::Dumper is kind of lying about non-existing elements.

    ²) and hashes have the advantage that the number of existing keys is readily available. The length of an array with gaps OTOH is of no help, especially because after a delete all non-existing entries will be truncated from the end.

      Thanks @Rolf. I did change to using a hash, which works fine, it is just slightly messier when it comes to string concatenation because of the double quotes I find I need to put around the keys.

      Thanks for link, I had a read.
        > messier when it comes to string concatenation because of the double quotes I find I need to put around the keys.

        In Perl keys are auto-quoted!

        I.e. this "txt $h{key} txt" works fine.

        You are also free to use hash-slices for multiple values, like "txt $h{@keys} txt"

        update

        From Learning Perl

          Unquoted Hash Keys

          Perl offers many shortcuts that can help the programmer, such as omitting the quote marks on some hash keys.

          You can’t omit the quote marks on every key since a hash key may be any arbitrary string. But keys are often simple. If the hash key only consists of letters, digits, and underscores without starting with a digit, you may be able to omit the quote marks. This kind of simple string without quote marks is called a bareword since it stands alone without quotes.

          One place you are permitted to use this shortcut is the most common place a hash key appears, which is in the curly braces of a hash element reference. For example, instead of $score{"fred"}, you could write $score{fred}. Since many hash keys are like this, not using quotes is a convenience. But beware: if there’s anything inside the curly braces besides a bareword, Perl will interpret it as an expression.

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

Re: Using exists to check element in array
by SankoR (Prior) on Jan 28, 2024 at 05:44 UTC
      No, not autovivification.

      In fact quite different.

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

Re: Using exists to check element in array
by sectokia (Friar) on Jan 29, 2024 at 02:52 UTC

    Perl source and tests have had all use of 'exists' on array elements / array refs removed. So running exists on an array element can lead to surprising behavior - because no one is testing what the behavior should be. So essentially the maintainers are reserving the right to modify this behavior by accident.

    This situation seems to have come about because originally "exists" on array elements returned true if Perl had allocated a CV pointer for that index, where as now it returns True only if a CV pointer is both allocated and is not set to null. But this change over was not clean and and I think there are situations where it would not always be true. Imagine an array that is extremely large and fragmented in its use of indexes, where memory allocation has trimmed, extended, realloacted various CV linked lists etc. Does perl always only have a CV for each actually 'used' index position? The answer is apparently no.

      I have problems to follow...

      > The answer is apparently no

      Is it possible to demonstrate this problem with Perl code?

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

      Thanks. This is really interesting. I thought under "normal" circumstances memory allocation for variables was fairly fixed and persistent for PERL and was the reason that 'referencing' works. Perhaps a little knowledge is truely dangerous but I have to push through to turn a little into a little-more, so thanks for the input. Does CV stand for "Code Value"? I found that in 'perlguts' which was pretty heavy reading!

      Musings... It seems to me that it is fairly reasonable for a user to want to know if an element of an array or hash 'exists'. Exists could return more than a binary choice and the user could be expected to test the returned value to make sure it is what they wanted. I am guessing that a fairly large portion of PERL code includes arrays and hashes, so predictable results in this area now and in the future seems important. I wonder what code may break in the future and indeed where I might have used it previously and erroneously! The warning certainly made me stop but I needed the why and the guidance on what to do instead.

      Thanks again to you and the other contributors.

        Its because the 'high level' concept of an Array is that it is a string of 0 to n items. So all items in the array always exist conceptually.

        So if you want to know if index x 'exists' in the array, simply do either: (x <= $#array) or (x < scalar @array).

        If you haven't set an array index to a value, then its value will be undef and you can check this simply with (defined $array[x]). If x is beyond the length of array, this will not change the length of the array. However if you assign an index to a value - including undef, the array will expand in size to at least that index.

        .
Re: Using exists to check element in array
by ikegami (Patriarch) on Jan 29, 2024 at 14:19 UTC

    Use defined instead of exists.

      This won't help with existing undefined values.

      Exist is the logical choice in his use case, and he asked for the rationale.

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

        and he asked for the rational.

        Hasn't that been answered already?

        It's because exists $a[i] doesn't really check if the element exists in the array. It's not sufficient for the element to be part of the array; the pointer in the array buffer must be something other than NULL.

        For example,

        my @a; $a[1] = undef; $a[2] = 0; $a[3] = 7;
        Element of the arrayexistsdefinedTrue
        $a[0]YesNoNoNo
        $a[1]YesYesNoNo
        $a[2]YesYesYesNo
        $a[3]YesYesYesYes
        $a[4]NoNoNoNo

        It might also be warning of problems with using NULL as a special value. I suspect Perl is quite consistent at keeping NULL elements NULL, but there might be surprise cases (for (@a)? for ((), @a)? f(@a)?)

        Update: Replaced some uses of "exists".

        It will help if undef isn't an acceptable value, which is a common scenario.

        I should have qualified my answer, but I thought the OP said they were storing numbers. Those are the keys, though. woops.