3

So, I believe this has something to do with the difference between arrays and lists, but I don't understand what's going on here. Can anyone explain how and why Perl treats an expression like (1..4) differently than (1, 2, 3, 4) and @{[1..4]}?

$ perl -de1

Loading DB routines from perl5db.pl version 1.31
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1): 1
  DB<1> x scalar (1,2,3,4)
0  4
  DB<2> x scalar (1..2,3,4)
0  4
  DB<3> x scalar (1,2..3,4)
0  4
  DB<4> x scalar (1,2,3..4)
0  ''
  DB<5> sub foo { (1..4) } # (the actual problem case, except 4 would be a variable)
  DB<6> x scalar foo()
0  ''
  DB<7> sub bar { @{[1..4]} } # (the workaround)
  DB<8> x scalar bar()
0  4
  • 2
    For further enlightenment, try `print scalar (5,6,7,8)` and `print scalar (@a=(5,6,7,8))` and maybe even `print scalar (()=(5,6,7,8))` – mob Jul 29 '10 at 22:08

1 Answers1

11

The "range operator" that produces a list just doesn't exist in scalar context -- the .. operator when used in scalar context is the "flip-flop operator" instead. The flip-flop operator starts off returning false, continues returning false until its left argument becomes true, then continues returning true until its right argument becomes true, after which it returns false and returns to its initial state. If all of that sounds pretty useless, consider

while (<>) {
  print if /BEGIN/ .. /END/;
}

The .. operator starts out false, so lines from the file aren't printed. But once the regex /BEGIN/ matches, the flip-flop becomes true, and all of the following lines will be printed, until the regex /END/ matches. From the line following "END", the flip-flop will be false again. So the effect is to only print lines between (and including, but you can control that) "BEGIN" and "END".

There's a little more magic with $. if either side is constant, but that's not really important. Read more on the flip-flop operator in perldoc perlop.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • That operator sure is popular today! – Ether Jul 29 '10 at 22:08
  • Whee! I suppose I was expecting the expression to be evaluated in list context and then scalar'd. `perldoc -f scalar` hints at it, I suppose, but is ambiguous at best: *There is no equivalent operator to force an expression to be interpolated in list context because in practice, this is never needed. If you really wanted to do so, however, you could use the construction `@{[ (some expression) ]}`, but usually a simple `(some expression)` suffices.* –  Jul 29 '10 at 22:12
  • 1
    @fennec yeah, what you initially expected simply doesn't happen. List and scalar context are distinct, and nothing is "evaluated in list context and then scalar'd" unless you specifically force that to happen. Many operators will behave *as if* that was the case (having equivalent side-effects in either context, and either returning a list in list context and a count in scalar context, or returning a single value in either context) but some will surprise you by doing something significantly different in one context than another. `m//g` and `split` are other ones to look out for. – hobbs Jul 29 '10 at 22:29
  • Yeah, I'm theoretically aware of that on some level, but the original code I was using was actually counting thingies with `scalar $x->thingy_indices` where `sub thingy_indices { +(1, 2) }` or `sub thingy_indices { +(1, 2, 3) }`. and it was getting back values `2` and `3` as expected. This effectively poisoned my mind for the day. :b –  Jul 29 '10 at 22:47
  • fennec: scalar's doc is not the place to have looked, since each operator has its own idea of what to do in scalar vs list context. – ysth Jul 30 '10 at 04:28