While programming stuff last night, I ran into something that correlated with this node on return values & exceptions that caught me a bit off guard.

Specifically, this code:

#!/usr/bin/perl -w use strict; my %hash; print defined( %hash )?"yea":"nay","\n";
returned a message that defined() was deprecated.
This was in 5.6.1; this is not the case for 5.005. Flipping to the recent docs for this, I discover this is truely the case, and that you are encouraged to use:
#!/usr/bin/perl -w use strict; my %hash; print ( %hash )?"yea":"nay","\n";
which doesn't seem to terrible in itself. This only applies to it's use for hashes and arrays (references and scalars are quite happy with defined()). But consider that one of the issues in the aforementioned node was what to return; my personally style is that if there's a non-critical function, I'll return undef if the error occured, otherwise what is needed; this is similar to how many other perl built-ins work.

However, consider a function that can return a hash or array; both of these may be empty due to how data is stored or the like; for example, in my Tie::Hash::Stack, the hash is initially tied to a blank hash; this is still valid, but doing a test such as if ( %hash ) ... will fail with the empty hash; same can happen to a purposely empty array. In these cases, I'd expect that an empty hash or array reflects that no errors were encountered and the hash/array created successfully, while undef would tell me that there was a problem. For example:

sub all-between { my ($min, $max ) = @_; if ( $min > $max ) return; my @array = (); my $i = $min + 1; while ($i < $max) { push @array, $i++; } return @array; } my @ar1 = all-between( 4, 10 ); # Gives (5,6,7,8,9) my @ar2 = all-between( 4, 5 ); # Gives () my @ar3 = all-between( 4, 3 ); # Gives undef if ( my @ar = all-between( $arg1, $arg2 ) ) { # as opposed to: # if ( defined( my @ar = all-between( $arg1, $arg2 ) ) ) { print "Your allbetween are " , join( ',', @ar ), "\n"; }
(despite the lameness) will now break in select cases because of this change.

Thus, the question is, is there a valid replacement for defined() for arrays and hashes, or will we need to add an extra step of logic for these types of situations?


Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain

Replies are listed 'Best First'.
(tye)Re: Replacement for defined(%)/defined(@)?
by tye (Sage) on Jun 29, 2001 at 21:53 UTC

    You should never use defined on hashes nor on arrays and you should never use exists nor delete on array elements. These all tell you stuff about whether memory has been allocated for the items, which is an internals thing that no script should ever have to care about. The correct replacements are:

    defined %hash scalar keys %hash defined @array scalar @array exists $array[$elt] defined $array[$elt] (and/or) 0 <= $elt && $elt < @array delete $array[$elt] undef $array[$elt] (or) splice( @array, $elt, 1 )
    You can leave off the scalar call in many cases, of course.

    Also, you are mistaken about return;. It doesn't return undef in a list context, it returns () (it does return undef in a scalar context). So you need to fix your function and you'll probably have to return that error condition "out of band". If your existing code happens to work in that case (I don't think it would, but I haven't tested), then that is more by accident than by design.

    Update: No, the current code doesn't even work:

    sub all_between { my ($min, $max ) = @_; if ( $min > $max ) { return; } my @array = (); my $i = $min + 1; while ($i < $max) { push @array, $i++; } return @array; } my @ar1 = all_between( 4, 10 ); # Gives (5,6,7,8,9) my @ar2 = all_between( 4, 5 ); # Gives () my @ar3 = all_between( 4, 3 ); # Gives undef print "\@ar1 is defined\n" if defined @ar1; print "\@ar2 is defined\n" if defined @ar2; print "\@ar3 is defined\n" if defined @ar3;
    yields only: @ar1 is defined

    Update 2: I never before noticed that an empty hash doesn't report its number of buckets in a scalar context.

            - tye (but my friends call me "Tye")
      I guess the question starts to boil down to is:

      Does the so-called empty set (or hash) exist in Perl, and if so, is it possible to distiquish it from the undef case?

      Let me back up and go back to my problem from last night. I'm writing test cases for Tie::Hash::Stack, and in the creation set, I want to make sure that the hash was actually tied, as it's possible to send a parameter that breaks the function (normally the function would catch and die at that point, but for test cases, I want to catch that problem). So I used something similar to this (yes, I know defined is wrong here, but stay with me...):

      my %hash; tie( %hash, "Tie::Hash::Stack" ); print defined( %hash ) ? "ok" : "not ok";
      Now, the object that I create with T:H:S is an array of hashes, and upon creation, an empty hash is pushed onto the array (e.g.: "( {} )" ). Thus, the hash may be empty, but it is certainly not undefined. Thus, this code:
      my %hash; tie( %hash, "Tie::Hash::Stack" ); print ( %hash ) ? "ok" : "not ok";
      fails to note the creation of the tie'd variable. At the time, I didn't know that tie returned anything that I could use to judge success or failure (note that the perlfunc:tie do not state anything about a return value, had to find that out for myself, which appears to be the object that the hash was tied to), but after I got to this problem, I eventually got to:
      my %hash; my $obj = tie( %hash, "Tie::Hash::Stack" ); print defined( $obj ) ? "ok" : "not ok"; # or simply $obj ? "ok" : "not ok";
      This works, there's no problems, so I can happily continue along wrt T:H:S.

      But the underlying problem of the fact that an empty array or hash is undistinguisable from the undef case somewhat bothers me. The example code in my original post, for example, would require a change of logic as tye's pointed out in order to have the empty set and an error code separated. If I was really dealing with, on mathematical terms, of the empty set, I'd probably revert to some code like Functional.pm that gives me an object that acts like the empty set.


      Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain

        Let me expand a bit:

        sub undefined { return; } sub empty { my @array= (); return @array; } my @undefd; my @empty= (); my @dedefd= (1,2,3); @dedefd= undefined(); my @emptied= (1,2,3); @emptied= empty(); my @undef2= undefined(); my @empty2= empty(); print "\@undefd is defined\n" if defined @undefd; print "\@empty is defined\n" if defined @empty; print "\@dedefd is defined\n" if defined @dedefd; print "\@emptied is defined\n" if defined @emptied; print "\@undef2 is defined\n" if defined @undef2; print "\@empty2 is defined\n" if defined @empty2;
        yields just:
        @dedefd is defined @emptied is defined

        You can have an undefined array but there is no such things as an undefined list value. So you can't pass around arrays by value and successfully worry about whether they are defined or not. To make an array undefined, you have to write undef @array and to have a subroutine do that would require that a reference to the array be passed in.

        So you really shouldn't try to have two types of empty arrays/hashes. Unlike strings where there is the empty string and undef, Perl doesn't provide a special value for undefined arrays/hashes and doesn't make it easy for you to provide one of your own.

        If I need to distinguish an empty list from an error, then I'd probably return a reference to an empty array for the first case and a "false" value for the second.

                - tye (but my friends call me "Tye")
        The "thing" called %hash exists. It may have nothing in it, but it never becomes something other than a hash.

        undef is a scalar concept. A scalar can be a number, a string, various other things, or undef which is a special =value= meaning none of the above. A hash doesn't hold a value -- it is a tree of values. Only individual hash elements can =hold= undef.

        If you are asking if %hash is in the symbol table at all, it is, since compiling the name %hash in that statement does it! So use the symbol-table hash ($main::{hash}, I think) to see if that's even in the table, but it won't tell you that there is no %hash specifically, but no $hash, @hash, etc. as well. Seeing if the HV in the glob is getting into guts, but look at Devel::Peek module to do that.

        Point is what you suspect: whether the hash has zero entries in the normal way, or whether the structure has not been allocated at all yet is an internal implementation detail, not visible to the language. Conceptually, the container always exists, though it may be empty. Just as a scalar =exists= but holds the value called undef, instead of 42. There is no special value for the container that fills an analogous role.

        —John