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

As a good learning excercise, I am attempting to reimplement Tie::IxHash without looking at the source code. Certain things are bugging me though. FIRSTKEY and NEXTKEY are described in perltie thusly:

FIRSTKEY this This method will be triggered when the user is going to iterate through the hash, such as via a keys() or each() call. sub FIRSTKEY { carp &whowasi if $DEBUG; my $self = shift; my $a = keys %{$self->{LIST}};# reset each() iterator each %{$self->{LIST}} } NEXTKEY this, lastkey This method gets triggered during a keys() or each() iteration. It has a second argument which is the last key that had been accessed. This is useful if you're carrying about ordering or calling the iterator from more than one sequence, or not really storing things in a hash anywhere. For our example, we're using a real hash so we'll do just the simple thing, but we'll have to go through the LIST field indirectly. sub NEXTKEY { carp &whowasi if $DEBUG; my $self = shift; return each %{ $self->{LIST} } }

Going by the names of these functions, I would assume that they ought to return only the next key to use. The fact that they end with each, though, makes me wonder if they should behave as each actually does: that is, keys and values in list context, keys only in scalar context. If keys and values were only the other way around, I could return ($value, $key), because in scalar context left args are thrown away. Since that isn't the case, though, I'm forced to use wantarray. So far so good.

The problem, though, is the FIRSTKEY (and NEXTKEY) always seem to be called in scalar context. Here is my test code:

package Foo; sub TIEHASH { bless {}, shift; }; sub FIRSTKEY { wantarray ? print "Array\n" : print "Scalar\n" }; *NEXTKEY = *FIRSTKEY; sub FETCH { return }; package main; tie %me, Foo; () = each %me; scalar each %me; __END__ Scalar Scalar

The two conclusions that I can doraw from this are a) it only wants the keys, and thus is always called in scalar context, or b) that wantarray isn't always right in cases like these. My main idea for why this might be the case is evident in Overloading for List Context. Though that was with overloading, my concern is that perl internals which tie deals with don't do the context thing per-wantarray.

The only way that I could see dealing with b) in a way consistent with the docs but which doesn't use wantarry is to make a temp array and use each on that, but that's hackish and ugly.

Any other thgouhts on the dealio?



Who is Kayser Söze?
Code is (almost) always untested.

Replies are listed 'Best First'.
Re: (FIRST|NEXT)KEY in context
by BrowserUk (Patriarch) on Jan 09, 2004 at 07:52 UTC
    ... a) it only wants the keys, and thus is always called in scalar context, ...

    Bingo :). If you think about it, what would FIRSTKEY or NEXTKEY return in a list context? There can only be one FIRSTKEY; and only one NEXTKEY at any given point. Each time either of these is called, you should return exactly one key. It is up to you to retain your position internally, and reset that position whenever FIRSTKEY is called.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Timing (and a little luck) are everything!

Re: (FIRST|NEXT)KEY in context
by ysth (Canon) on Jan 09, 2004 at 18:18 UTC
    Yes, they are (and should be) always called in scalar context, and should just return the key.

    Starting with 5.8.3, FIRSTKEY may also be called by scalar(%tiedhash) if there is no SCALAR method. See perl583delta, and the perltie.pod from 5.8.3.

    Good, I see you have an explicit iterator reset (though with an unneeded my $a =) in your FIRSTKEY. That's necessary, because it's up to perl to keep track of whether FIRSTKEY or NEXTKEY should be called. When keys %tiedhash is used in a void context, for instance, no tie method will be invoked; perl just reset's its iterator, so a subsequent call to each will use FIRSTKEY. But it's up to your class to maintain an iterator also, so that NEXTKEY will return the appropriate value.

Re: (FIRST|NEXT)KEY in context
by johnvk (Initiate) on Feb 12, 2010 at 06:20 UTC

    this thread is 6 years old; nevertheless, i can't make each() work on a tied hash :(

    it is clear from the perltie documentation that the implementaion of each() is via FIRSTKEY and NEXTKEY. if that is so, then they must return a list in list context.

    witness each documentation:

    When called in list context, returns a 2-element list consisting of the key and value for the next element of a hash, so that you can iterate over it. When called in scalar context, returns only the key for the next element in the hash.

    (emphasis mine)

    if the each() documentation is true, then FIRSTKEY/NEXTKEY simply must return a list in list context.

    and they do, but only if the return value of each() returned directly.

    witness this example code:

    use warnings; use strict; $\ = $/ = "\n"; sub ifdef { defined ($_[0]) ? $_[0] : ifdef($_[1], 'undef'); } ################################################## # # return each directly # package TIED_HASH_1; use Tie::Hash; our @ISA = qw(Tie::StdHash); sub FIRSTKEY { print "TIED_HASH_1 FIRSTKEY " . (wantarray ? "array" : "scalar"); my $a = scalar keys %{$_[0]}; return each %{$_[0]}; } sub NEXTKEY { print "TIED_HASH_1 NEXTKEY " . (wantarray ? "array" : "scalar"); return each %{$_[0]}; } ################################################## # # return each INdirectly # package TIED_HASH_2; use Tie::Hash; our @ISA = qw(Tie::StdHash); sub FIRSTKEY { print "TIED_HASH_2 FIRSTKEY " . (wantarray ? "array" : "scalar"); my $a = scalar keys %{$_[0]}; my ($x,$y) = each %{$_[0]}; print "x='$x' y='$y'"; # note $x and $y are fine return ($x, $y); } sub NEXTKEY { print "TIED_HASH_2 NEXTKEY " . (wantarray ? "array" : "scalar"); my ($x,$y) = each %{$_[0]}; print "x='$x' y='$y'"; # note $x and $y are fine return ($x, $y); } ################################################## # package main; my %h_untied; my %h_tied_1; my %h_tied_2; tie %h_tied_1, 'TIED_HASH_1'; tie %h_tied_2, 'TIED_HASH_2'; $h_untied{unt_a}=11; $h_untied{unt_b}=22; $h_tied_1 {t_1_a}=11; $h_tied_1 {t_1_b}=22; $h_tied_2 {t_2_a}=11; $h_tied_2 {t_2_b}=22; my ($k, $v); my @res; print "-------------------- untied hash works fine"; ($k, $v) = each %h_untied; print "k='$k' v='$v'"; ($k, $v) = each %h_untied; print "k='$k' v='$v'"; print "-------------------- tied hash returning each's return directly + works fine"; ($k, $v) = each %h_tied_1; print "k='$k' v='".ifdef($v)."'"; ($k, $v) = each %h_tied_1; print "k='$k' v='".ifdef($v)."'"; print "-------------------- tied hash returning its own list is brok"; ($k, $v) = each %h_tied_2; print "k='$k' v='".ifdef($v)."'"; ($k, $v) = each %h_tied_2; print "k='$k' v='".ifdef($v)."'";

    Output:

    -------------------- untied hash works fine k='unt_b' v='22' k='unt_a' v='11' -------------------- tied hash returning each's return directly works +fine TIED_HASH_1 FIRSTKEY scalar k='t_1_b' v='22' TIED_HASH_1 NEXTKEY scalar k='t_1_a' v='11' -------------------- tied hash returning its own list is brok TIED_HASH_2 FIRSTKEY scalar x='t_2_b' y='22' k='22' v='undef' TIED_HASH_2 NEXTKEY scalar x='t_2_a' y='11' k='11' v='undef'

    you can see that each() called on TIED_HASH_2 simply does not work.

    The only explanation i can see for this is that each() uses its own wantarray; some kind of wantarray_special_for_each or something.

    but i come to the monks seeking wisdom because maybe there's something else going on here i can't see.

    UPDATE: (forgot to include output of this code. 1am postings come with some risks ;) and fixed slight code bug)

        i'm not sure if you were answering my question or trying to ask a question yourself.

        In your case, the reason why you get an infinite loop is that the keys() call resets the iterator that is shared among each(), keys() and values();. so in your loop the iterator gets set to the beginning every iteration by the call to keys()

        but the docs you pointed to explain that. so i presume you knew that. if this is supposed to be an answer to my query, i aplogize but i dont understand. in particular, my issue only comes up when the hash is tied.

        As for moving my query to its own thread, i actually wantd it here, because it appears the OP and i are asking the same question. I figured i'd keep perlmonks organized by keeping the topic all in one place. Are you saying i'll get less attn as a reply rather than an original post?

        Thanks.