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

Dear Monks

I tried to put a hash from a sub directly into a foreach loop, like this
#! /usr/bin/perl use strict ; use warnings ; foreach ( keys &testa ) { print "key is $_\n" ; } sub testa { my %s ; $s{'a'} = 10 ; $s{'b'} = 20 ; $s{'c'} = 30 ; return %s ; }
this looks like good perl programming to me, but it doesn't work however :(
(It makes me feel like a beginner)
Any suggestions ?

Thnx LuCa

UPDATE: I've update the sub and replace % into $

Replies are listed 'Best First'.
Re: hash from sub directly into foreach loop
by davido (Cardinal) on Jun 30, 2006 at 08:33 UTC

    There are a few problems. One is that %s{'a'} = 10; won't compile. You mean to say, $s{'a'} = 10;

    The next problem is that testa() returns a flat list of keys and values, not a hash. You could re-hashify them like this:

    %{ { testa() } }

    This takes the return value of testa() (a flat list of keys interlaced with values), absorbs that list into an anonymous hash (the inner set of {...} does this), and then dereferences that anonymous hash ref (the outter %{ ..... } does this).

    But even then you still have a problem. Your loop will print a list of keys, sure enough. But how are you going to access the values if nothing is holding onto that hash ref?

    Try this instead:

    while( my( $key, $val ) = each( %{ { testa() } } ) { print "Key: $key\tValue: $val\n"; }

    ...or better yet, pass by reference, as described in perlsub:

    while( my( $key, $val ) = each( %{ testa() } ) { print "Key: $key\tValue: $val\n"; } sub testa { my %s = ( a => 10, b => 20, c => 30 ); return \%s; }

    Dave

      sorry for all the confusion about the typo.

      But thnx for the %{ { &testa } } that does the trick!
      I guess it might be very usefull to study the perlsub (how do you guys make this a link, where is it documented, I've search the docs already a couple of times ?)!!

      LuCa

        You're welcome, but I did say %{ { testa() } }, not %{ { &testa } }. Your specific example code doesn't bump into the difference, but there is a difference. It's just good to be in the habit of using the subroutine() method of calling subs, instead of the Perl4-ish (and sometimes confusing) &subroutine method.

        As for how to link to perlsub, see About the PerlMonks FAQ, and specifically, What shortcuts can I use for linking to other information?. ...but first read perlsub; that's a much more important step toward your understanding Perl.


        Dave

Re: hash from sub directly into foreach loop
by davidj (Priest) on Jun 30, 2006 at 08:17 UTC
    First of all, the syntax in the sub is incorrect:
    %s{'a'} = 10 ; %s{'b'} = 20 ; %s{'c'} = 30 ;
    should be
    $s{'a'} = 10 ; $s{'b'} = 20 ; $s{'c'} = 30 ;
    anyway, the following change will work for you:
    #!/usr/bin/perl use strict ; use warnings ; foreach ( keys %{&testa} ) { print "key is $_\n" ; } sub testa { my $s = () ; $s->{'a'} = 10 ; $s->{'b'} = 20 ; $s->{'c'} = 30 ; return $s ; }
    hope this helps,
    davidj

      Note that:

      my $s = ();

      is equivelent to

      my $s = (undef);

      which is equivelent to:

      my $s;

      The first use of a hash ref autovivifies the referred hash making the previous assignment redundant (and misleading). Consider:

      use strict; use warnings; use Data::Dump::Streamer; my $s = (); Dump ($s); $s->{'a'} = 10 ; Dump ($s);

      which prints:

      $VAR1 = undef; $HASH1 = { a => 10 };

      and note that the contents of $s changes from an undefined value to a hash ref.

      If you really want to indicate intent with an assignment use:

      my $s = {};

      DWIM is Perl's answer to Gödel
      nope, the sub should return a hash and not a hash reference. It should be the same as with
      foreach( keys CGI::Vars() )
      because that is what I'm trying to do!

      LuCa

        You can't "return a hash" from a subroutine, you can only return a list, the effect of return %hash is to flatten the hash into a list of key => value pairs. Thus the effect of the the three following subroutines is similar (the order of the hash keys in the first aside:)

        sub foo { my %foo = ( Foo => '1', Bar => '2'); return %foo; } + sub bar { my @bar = qw(Foo 1 Bar 2); return @bar; } + sub baz { return 'Foo','1','Bar','2'; } + print foo(),"\n"; print bar(),"\n"; print baz(),"\n";

        This is why people are telling you to return a hash reference as this is the only way to retain the, er, hashiness after the appropriate dereferencing.

        /J\

        Please show working code which does what you're trying to do. When I try the following, it doesn't work:

        Q:\>perl -MCGI -le "foreach( keys CGI::Vars() ) { print }" Type of arg 1 to keys must be hash (not subroutine entry) at -e line 1 +, near ") ) " Execution of -e aborted due to compilation errors.

        When I try the following, which the documentation and davidj suggest, it works (in the sense of not being a syntax error). But that works by returning a hash reference:

        Q:\>perl -MCGI -le "foreach( keys %{ CGI::Vars() }) { print }" Q:\>
Re: hash from sub directly into foreach loop
by crabbie_upk (Initiate) on Jul 01, 2006 at 00:30 UTC
    Hi, I think here is the code you must be looking for.
    #!/usr/bin/perl use strict ; use warnings ; foreach ( keys %{&testa} ) { print "key is $_\n" ; } sub testa { my $s ; $s->{a} = 10 ; $s->{b} = 20 ; $s->{c} = 30 ; return $s ; }
    Cheers, Udaya.