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

Hello People,

I'm stumped, I was trying to write an obfuscation,
and (don't shoot me for this ;))) I included my own some kind of splittish thing.

And I found something VERY weird.

I wrote up the included code, and output, and I would
REALLY appreciate your view on this:
#!/usr/bin/perl -w use strict; sub Case1 { print "AtUnderscore: ",(join ",",@_),"\n"; print "shift: ",shift,"\n"; # string is now gone from @_ , which means # that $_[1] should be 5, and $_[2] is undef # This shows to be true (see output Case1) print "AtUnderscore after shift: @_\n"; print "\$_[1]: $_[1]\n"; print "\$_[2]: $_[2]\n"; }; sub Case2 { print "AtUnderscore: ",(join ",",@_),"\n"; # Please explain to me HOW I can shift @_ # here, but still have access to the original # $_[2] (see var $Rubbish)... my $Rubbish=join "",(split //,shift)[$_[1]..$_[2]]; # But HERE I lost the access to $_[2]. # its gone!!! print "Rubbish: $Rubbish\n"; print "1: $_[1]\n"; print "2: $_[2]\n"; }; print "\n\nCase1:\n"; Case1("This_is_a_string",3,5); print "\n\nCase2:\n"; Case2("This_is_a_string",3,5);
The resulting output :
Case1: AtUnderscore: This_is_a_string,3,5 shift: This_is_a_string AtUnderscore after shift: 3 5 $_[1]: 5 Use of uninitialized value in concatenation (.) at ./mysplit line 16. $_[2]: Case2: AtUnderscore: This_is_a_string,3,5 Rubbish: s_i 1: 5 Use of uninitialized value in concatenation (.) at ./mysplit line 34. 2:

Am I losing my mind, or just missing the point ?
Thank you in advance for any comment you might have

GreetZ!,

print "profeth still\n" if /bird|devil/;

Replies are listed 'Best First'.
Re: strange shift @_ problem
by ahunter (Monk) on Jan 18, 2001 at 16:50 UTC
    You are assuming that the expression is evaluated in a strict left-to-right order, which is not necessarily so, particularily when you use more than one type of operator. That is, in the second case,
    [$_[1]..$_[2]]
    is being evaluated before the shift is, so hence the @_ array is still as it was before the shift. Leftward list operators have a very high priority (see perlop), and this makes it look like perl evaluates the value of the index before the value of the list itself. To see this more clearly, try:
    sub flob { print "Flob"; return 1; } sub adob { print "adob"; return ( 1,2,3 ); } $a = (adob())[flob()]; print "\n$a\n";
    Which prints 'Flobadob', indicating that flob() is executed first. I can't find any docs that indicate that this is the expected behaviour, so it's probably not something you should rely upon working in future versions of perl.

    Andrew.

Re: strange shift @_ problem
by stefan k (Curate) on Jan 18, 2001 at 16:37 UTC
    Maybe I don't get your problem fully, but the behaviour of your code seems sensible to me. Personally I try to avoid shift whenever I can (usually by assigning paramters vi my ($v1,$v2) = @_; So try
    my $Rubbish=join "",(split //,$_[0])[$_[1]..$_[2]];
    regards Stefan Kamphausen
    $dom = "skamphausen.de"; ## May The Open Source Be With You! $Mail = "mail@$dom; $Url = "http://www.$dom";
Re: strange shift @_ problem
by Anonymous Monk on Jan 18, 2001 at 16:38 UTC
    shift means deleting the 1st value, which is @_[[0]]
    array starts from 0, I think
Re: strange shift @_ problem
by repson (Chaplain) on Jan 19, 2001 at 09:13 UTC
    For a bit more information have a look at this output from the compiler backend B::Terse.

    This is the statement print ((split(//,shift(@_)))[$_[1]..$_[2]]);

    LISTOP (0x817e1d0) print OP (0x817e1f8) pushmark BINOP (0x817e1a8) lslice UNOP (0x817e160) null [141] OP (0x817e188) pushmark UNOP (0x817e0f8) null UNOP (0x817e0d8) flop UNOP (0x817e0b8) flip [6] LOGOP (0x817e090) range [5] UNOP (0x817dfe0) null [127] UNOP (0x817dfa0) null [125] SVOP (0x817df80) aelemfast GV (0x +80f6760) *_ OP (0x817dfc0) null [5] UNOP (0x817e068) null [127] UNOP (0x817e028) null [125] SVOP (0x817e008) aelemfast GV (0x +80f6760) *_ OP (0x817e048) null [5] UNOP (0x817e118) null [141] OP (0x817e140) pushmark LISTOP (0x817df00) split [4] PMOP (0x817ddf0) pushre // UNOP (0x817dee0) shift UNOP (0x817dec0) rv2av [3] SVOP (0x817de30) gv GV (0x80f6760) *_ SVOP (0x817df28) const IV (0x80fa958) 0
    Now I don't know about you but I can sort of follow much of that, but the important part is seeing that the lslice line comes before the shift one. Which I assume means that it is dealt with first.
      "Careful there, good friar."

      The lslice opcode comes before the shift in this output of B::Terse -- but keep in mind that B::Terse by default shows an optree, which is empatically not in execution order.

      lslice is a BINOP, which means it has two children. The first child is the list of elements it will be grabbing (in this case, that comes from the range operator in list context, aka flip/flop in our opcodes above). The second child is the list from which we'll actually be slicing. The fact that the children are ordered that way is the reason the $_[1] etc. are evaluated first.

      For an easier way to figure out what order things happen in, try the -exec option to Terse, like so:

      perl -MO=Terse,exec -e'print +(split//,shift)[$_[1]..$_[2]]'

      I won't show you the output, because I have one more tip. Just a few weeks ago, Stephen McCamant released a new backend module that blows Terse out of the water. It's called B::Concise, and while it's only been added to the standard distro for very recent bleadperls, you can download it from the APC. Once you've got it, stick it somewhere in your INC path under a `B' directory, and use it like so:

      [~] $ perl -MO=Concise -e'print +(split//,shift)[$_[1]..$_[2]]' h <@> leave vKP/REFC ->(end) 1 <0> enter ->2 2 <;> nextstate(main 1 -e:1) v ->3 g <@> print vK ->h 3 <0> pushmark s ->4 f <2> lslice lK/2 ->g - <1> ex-list lK ->8 4 <0> pushmark s ->5 - <1> null lK/1 ->- 7 <1> flop lK ->8 j <1> flip[t4] lK ->8 5 <|> range(other->6)[t3] lK/1 ->i - <1> ex-aelem sK/2 ->j - <1> ex-rv2av sKR/1 ->- i <$> aelemfast(*_) s/1 ->j - <0> ex-const s ->- - <1> ex-aelem sK/2 ->7 - <1> ex-rv2av sKR/1 ->- 6 <$> aelemfast(*_) s/2 ->7 - <0> ex-const s ->- - <1> ex-list lK ->f 8 <0> pushmark s ->9 e <@> split[t2] lK ->f 9 </> pushre(//) s ->a c <1> shift sK/1 ->d b <1> rv2av[t1] sKRM/1 ->c a <$> gv(*ARGV) s ->b d <$> const(IV 0) s ->e

      Or, to see the exec order:

      [~] $ perl -MO=Concise,-exec -e'print +(split//,shift)[$_[1]..$_[2]]' 1 <0> enter 2 <;> nextstate(main 1 -e:1) v 3 <0> pushmark s 4 <0> pushmark s 5 <|> range(other->6)[t3] lK/1 6 <$> aelemfast(*_) s/2 7 <1> flop lK goto 8 i <$> aelemfast(*_) s/1 j <1> flip[t4] lK 8 <0> pushmark s 9 </> pushre(//) s a <$> gv(*ARGV) s b <1> rv2av[t1] sKRM/1 c <1> shift sK/1 d <$> const(IV 0) s e <@> split[t2] lK f <2> lslice lK/2 g <@> print vK h <@> leave vKP/REFC

      As you can see from the above, the aelemfast (which accesses elements of the array in the glob *_) opcodes execute before the shift.

      -dlc

Re: strange shift @_ problem
by flocto (Pilgrim) on Jan 18, 2001 at 17:05 UTC
    Ok, you have a little mistake here.. It's all in "Case2":

    my $Rubbish=join "",(split //,shift)[$_[1]..$_[2]];

    Ok, before that you have $_[0], $_1 and $_2. Then you shift. That means that you take the first value ($_[0]), end delete it. So $_1 becomes $_[0] and $_2 becomes $_1. And since there wasn't a $_3 there won't be a $_2 after that.
    If you alter the line to the following it should work:

    my $Rubbish=join "",(split //,shift)[$_[0]..$_[1]];
    sorry, i made a mistake.. this WON'T work! see below..
    Regards, octopus -- GED/CC d-- s:- a--- C++(+++) UL+++ P++++$ L++>++++ E--- W+++@ N o? K? w-- O- M-(+) V? !PS !PE !Y PGP+(++) t-- 5 X+ R+(+++) tv+(++) b++@ DI+() D+ G++ e->+++ h!++ r+(++) y+
      Thanks, but it doesn't help ;)))

      Because it DOES work the way it is written,
      and the way you write it, it tries to use the string
      as the first index of the slice on the thing ;)))

      ahunter had a great point there...

      but.. This IS the first time Perl does something I DON'T
      expect ;))

      GreetZ!,
        ChOas

      print "profeth still\n" if /bird|devil/;
        Hm, I think I see now what you mean. You can access $_2 in that line I posted but not after that line was processed.
        AFAIK, perl replaces all variables first (like $_2) and then processes the commands (like shift). That would result in:
        First $_2 was replaced with it's value (3 or 5 or whatever), and then perl uses the shift command and there is no $_2 no more..
        Maybe that helps!?

        Regards, octopus
        --
        GED/CC d-- s:- a--- C++(+++) UL+++ P++++$ L++>++++ E--- W+++@ N o? K? w-- O- M-(+) V? !PS !PE !Y PGP+(++) t-- 5 X+ R+(+++) tv+(++) b++@ DI+() D+ G++ e->+++ h!++ r+(++) y+