Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Know Your Contexts


brian has been a Perl user since 1994. He is founder of the first Perl Users Group, NY.pm, and Perl Mongers, the Perl advocacy organization. He has been teaching Perl through Stonehenge Consulting for the past five years, and has been a featured speaker at The Perl Conference, Perl University, YAPC, COMDEX, and Builder.com. Contact brian at [email protected].


Quick, without looking, next to this code, write what ends up in $x.

@y = ( 50 .. 60 );
$x = ( 4, 5, @y );

This issue came up recently on perl5-porters (bug report 34582), and I wasn't paying that close attention. Randal Schwartz posed this problem to me from iChat, and without thinking I said 60, because that would be the last element in the list expansion on the right hand side. Well, I was wrong. The answer is 11.

Maybe you were wrong, too. Let me give you another one that makes the situation a bit more clear.

$x = ( 3, 4, localtime );

Now what ends up in $x? You still aren't peeking yet, are you? Once I was told I was wrong in the first case, I got this one right.

Let me explain: The general rule (and the one I should know well because I teach it in Stonehenge's "Learning Perl" course), is that you figure out the context from the left-hand side of the statement. What's the context in the two examples? In each case, $x is obviously a scalar, so it's scalar context. That's not a big shocker to most people, but now look at the right-hand side. In these examples, I have parentheses around a bunch of items. I might even call that bunch of items a list. I sometimes even call the parentheses and commas the "list creation operator." However, how does Perl evaluate elements inside that list? Be careful now—I wouldn't have anything to write about if the "best feeling" answer was also the actual one.

Since the left-hand side forces scalar context, Perl evaluates each of the items on the right-hand side in scalar context, too. Before I explain the right answers, remember what happens for a flat list of literals on the right-hand side: $x ends up with the last element from the comma operator, so it gets the literal 6.

$x = ( 4, 5, 6 )  # $x gets 6

Now let's go back to my first example, where @y is the last item in the bunch. Here it is again so you don't have to find it, and if you've printed this and wrote your answer next to the first example, I'm giving you a clean slate now that you know there is a trick.

@y = ( 50 .. 60 );
$x = ( 4, 5, @y );

Perl evaluates the @y in scalar context. In scalar context, an array evaluates to the count of its items. The Perl interpreter doesn't expand @y to make the list then take the last element, so instead of 60, which I guessed without looking too closely, the real answer is 11, which is the number of elements in @y.

I could have been more tricky and given you this example, which appears to show a "one-off" error if I'm expecting the last element in @y, which is 10. I still get 11 because that's the number of elements in @y. Only do this in an Obfuscated Perl Contest please!

@y = ( 0 .. 10 );
$x = ( 4, 5, @y );

Now you're ready to answer the second example, which I show again. What shows up in $x?

$x = ( 3, 4, localtime );

If you said a date-time string showed up in $x, you're right! In scalar context, the localtime() built-in returns a single, human-readable string. When I ran this, I got this string:

Thu Apr 27 04:01:32 2005

Now you have to watch out for functions that do different things in scalar and list context, and unfortunately for you, there is no easy way for you to force list context here.

Test your context kung-fu now. What does this put into $x?

$x = ( <STDIN>, <STDIN>, <STDIN> );

It reads the first line on STDIN and discards it, then does the same for the second line. It reads the third line and puts it in $x. Perl evaluates each line input operator in scalar context, so each one returns one line. Well, that's not quite right: It actually evaluates the parts it won't save in void context, but I'll gloss over that. The situation is a lot different when the left hand side forces list context, where Perl reads all of the lines of input in the first <STDIN>, then assigns the first line and the first line only to $x, and then it discards the rest of the lines. I haven't even gotten to the last two <STDIN> operators yet and we've already exhausted standard input. The next two <STDIN> return undef because I've already reached end-of-input.

( $x  ) = ( <STDIN>, <STDIN>, <STDIN> );

Now that you know how this context trick works, I'm going to ratchet up the difficulty level just a bit. We already know the general rule that the left-hand side forces the context, and we know perl evaluates in scalar context the items on the right-hand side. What ends in $x in this case?

$x = ( 4, 5, 6..10 );

By this time, you are probably peeking, and that's okay. For those of you who already figured this out, just be patient. The result is not the same as the list expansion, just like it hasn't been in all of the previous examples. It is not the same as this assignment:

$x = ( 4, 5, 6, 7, 8, 9, 10 );

If you don't know why you get a False value, try this program. Enter some lines on standard input, and make sure you enter at least six lines.

#!/usr/bin/perl

while( <> )
	{
	my $x = ( 1, 2, 3 .. 5 );
	print "Line $. ==> x is [$x]\n";
	}

Here's the output I got when I simply entered the names of the "Flintstones" characters as my lines of input. After the first line, $x is the empty string (you can test for defined() if you like), and $x stays like that until the third line. Have you figured it out yet?

fred
Line 1 ==> x is []
barney
Line 2 ==> x is []
betty
Line 3 ==> x is [1]
pebbles
Line 4 ==> x is [2]
bam-bam
Line 5 ==> x is [3E0]
dino
Line 6 ==> x is []

You might have to look in the perlop manpage to remind yourself that what you think is the range operator in that program is actually a completely different beast. In scalar context, the .. is the flip-flop operator, which returns False until its left-hand side evaluates to True, then returns True until its right-hand side evaluates to True, and after that, it returns False. It's okay if you have to read that a couple times.

It gets even more tricky, though, because when the flip-flop operator sees integer literals one either side of the .., it transforms itself into a longer form. That situation is actually a convenience form to compare the integer literal to the input line number, which is the $. Perl special variable.

$x = 3 .. 5;

$x = ( $. == 3 ) .. ( $. == 5 );

That means that $x will get a False value until I'm on the third line of input, which makes ( $. == 3 ) True. Once I'm on the third line, $x gets a True value, and it keeps getting a True value until I get to the fifth line, which makes ( $. == 5 ) True. Once I'm past the fifth line, I'll get False values again. Thus, I only get a True value in $x between the third and fifth lines. That may be handy if I want to extract certain lines of a file.

I just happen to know that Perl does that because I teach Perl. I don't think I have ever used that in any real programming in the entire 10 years I've been programming Perl, so I can understand how people can get unexpected (but correct!) results with that form of the flip-flop operator.

After this article, you should know enough Perl to win a couple of bar bets. You don't have to tell your buddies the secret until they pay up, but remember the rule for yourself: Perl evaluates the items on the right-hand side in the context the left-hand side forces. If you are assigning to a scalar, Perl's going to evaluate the items in scalar context.

TPJ


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.