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

I was meandering through demo_calc.pl in the Parse::RecDescent demo directory and came across this
sub evalop { my (@list) = @{[@{$_[0]}]}; my $val = shift(@list)->();

I took the line that confused me step-by-step and don't get the purpose of this. Working from inner to outer:

@{$_[0]} # easy --- deference an array reference [ @{$_[0]} ] # ok --- turn it back into an array ref.. why? @{ [ @{$_[0]} ] } # umm -- uh.... well, the @sign implies # we have an array, but how is it # different from the first array we # dereferenced?

Replies are listed 'Best First'.
Re: Multiple Levels of Array Dereferencing Have me Stumped
by cianoz (Friar) on Nov 05, 2000 at 21:33 UTC
    you wrote:
    # umm -- uh.... well, the @sign implies # we have an array, but how is it # different from the first array we # dereferenced?
    It's a copy!
    see this example:
    my $ref1 = ['a','b','c']; my $ref2 = ['d','e','f']; my $refref = [$ref1, $ref2]; myfunc($refref); print $ref1->[0]; sub myfunc { my (@list) = @{[@{$_[0]}]}; print $list[0][0]; $list[0] = ['1','2','3']; }
    it prints 'aa' and not 'a1' because @{[(list of refs)]} has built a copy of 'list of refs' protecting $ref1 from being altered by the assignment inside the funcion
    my 2 cents...
      Yes, but is it a useful copy? The question was about an extra reference-dereference pair that makes a copy of a copy. What do you get for that work?

      Just about nothing. There is no reason that I can see to prefer:

      sub foo { my @foo = @{[@{$_[0]}]}; # etc }
      over
      sub foo { my @foo = @{$_[0]}; # etc }
      In fact try this:
      my $bar = ["hello", "world"]; foo($bar); print "$bar->[0]\n"; sub foo { # Make my copy. my @foo = @{$_[0]}; # Try to mess up the first element. $foo[0] =~ s/h/H/; }
      See? You get a private copy already.
        I was trying to explain "what" that code does and not "why"
        however you'r right: my (@list) = @{[@{$_[0]}]}; will just build an extra (useless) copy, i didn't verified it..
      But wait a moment. Isn't @array = @{$arrayref} a copy also? Modifying $array[0] should have no effect upon $arrayref->[0]. So the extra level of de-referencing is puzzling.

      Setting one array to another copies the values, and by de-referencing the first time, you're building an array on the right-hand-side from which to copy.

      i'd say the having the most correct answer is worth more than 2 cents.
      Occam's razor! It's great!

      [ar0n]

Re: Multiple Levels of Array Dereferencing Have me Stumped
by Kanji (Parson) on Nov 05, 2000 at 20:49 UTC
    I've never tried using @{[...]} outside of strings before but I'm assuming it does pretty much the same thing, which is briefly mentioned in perlref's Using References section ...

    Here's a trick for interpolating a subroutine call into a string:

        print "My sub returned @{[mysub(1,2,3)]} that time.\n";

    The way it works is that when the @{...} is seen in the double-quoted string, it's evaluated as a block. The block creates a reference to an anonymous array containing the results of the call to mysub(1,2,3). So the whole block returns a reference to an array, which is then dereferenced by @{...} and stuck into the double-quoted string. This chicanery is also useful for arbitrary expressions:

        print "That yields @{[$n + 5]} widgets\n";


(jcwren) RE: Multiple Levels of Array Dereferencing Have me Stumped
by jcwren (Prior) on Nov 05, 2000 at 21:05 UTC
    I can't tell you exactly how the dereferencing breaks down, but I can tell you that evalop() gets an array of arrrays, where each array element is a reference to a closure (I think I'm using the right term. If not, call them anonymous subroutines).

    The basic theory behind demo_calc.pl is that it builds a list of operations, where each operation (numbers, function, whatever) is a closure. It, in effect, is building a TIL, or threaded interpeted language. Unlike a language like Forth, the thread only survives for the life of the operation (but that really doesn't matter).

    I spent some time trying to use Data::Dumper to dump the thread, but because Data::Dumper doesn't know what to do with code refs, it really didn't break down well. The closest I came was the implementation below, which gives you a better feel for how it operates. It could be a little clearer, but I was starting to get frusterated with it all.

    I didn't test with range operators or variables, but basic operations like 1/2+2 vs. 1/(2+2)/4, etc.
    #! /usr/local/bin/perl -ws use strict; use Parse::RecDescent; use Data::Dumper; my %hashlist = (); my $keycount = 0; my $parse = Parse::RecDescent->new(<<'EndGrammar'); main: expr /\s*\Z/ { $item[1]->() } | <error> expr: /for(each)?/ lvar range expr { my ($vname,$expr) = @item[2,4]; my ($from, $to) = @{$item[3]}; sub { my $val; no strict "refs"; for $$vname ($from->()..$to->()) { $val = $expr->() } return $val; } } | lvar '=' addition { my ($vname, $expr) = @item[1,3]; sub { no strict 'refs'; $$vname = $expr->() } } | addition range: "(" expr ".." expr ")" { [ @item[2,4] ] } addition: <leftop:multiplication add_op multiplication> { my $add = $item[1]; my $ref = sub { ::evaler ($add); ::eval +op $add }; ::push ('addevalop', $ref) } add_op: '+' { my $ref = sub { print "Add...$_[0] + $_[1]\n"; $_[0] ++= $_[1] }; ::push ('add', $ref) } | '-' { my $ref = sub { print "Sub...$_[0] - $_[1]\n"; $_[0] +-= $_[1] }; ::push ('sub', $ref) } multiplication: <leftop:factor mult_op factor> { my $mult = $item[1]; my $ref = sub { ::evaler ($mult); ::ev +alop $mult }; ::push ('mulevalop', $ref) } mult_op: '*' { my $ref = sub { print "Mul...$_[0] * $_[1]\n"; $_[ +0] *= $_[1] }; ::push ('mul', $ref) } | '/' { my $ref = sub { print "Div...$_[0] / $_[1]\n"; $_[ +0] /= $_[1] }; ::push ('div', $ref) } factor: number | rvar | '(' expr ')' { $item[2] } number: /[-+]?\d+(\.\d+)?/ { my $ref = sub { $item[1] }; ::push ('n +umber', $ref) } lvar: /\$([a-z]\w*)/ { $1 } rvar: lvar { my $ref = sub { no strict 'refs'; ${$item[1]} } +; ::push ('rvar', $ref) } EndGrammar print "> "; while (<>) { print $parse->main($_), "\n\n> "; } # # This allows us to 'name' the closures. We append an ever-increment +ing number to the hash key so that # multiple add, subtract, whatever operations won't overwrite the exi +sting key. Remember that each # operation is a standalone closure, and are not 'packed' down to one + common routine for each function. # sub evaler { my $arr = shift; print "Eval ", join (', ', map {$hashlist {$_}} @$arr), "\n"; } # # Build the hashlist so we know what the closures refer to. # sub push { my ($name, $ref) = @_; my $val = sprintf ("%s_%d", $name, $keycount++); print "key=$ref, val=$val"; print ", number=", &$ref if ($name eq 'number'); print "\n"; $hashlist {$ref} = $val; # # Have to return the ref, because Parse::RecDescent expects it # return $ref; } sub evalop { print Data::Dumper->Dump ([\@_]); my (@list) = @{[@{$_[0]}]}; my $val = shift(@list)->(); while (@list) { my ($op, $arg2) = splice @list, 0, 2; $op->($val,$arg2->()); } return $val; }
    --Chris

    e-mail jcwren
Re: Multiple Levels of Array Dereferencing Have me Stumped
by Dominus (Parson) on Nov 26, 2000 at 23:29 UTC
    It looks to me like a mistake. I just wrote to Damian to ask about it. When I get an answer, I'll post.
      Damian Conway, the original author of the code in question, says:
      That's so baroque, I'm sure it must originally have been intentional, though it's now clearly vestigial.

      An earlier version probably did something like:

      foreach (@{[@{$_[0]}]}) {...}
      where the inline copy was essential, presumably to prevent $_ iterating the original arguments. And then I simply forgot to correct it when I changed the structure.

      I've changed it now. Thanks.