Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

NEWBIE Brain Teaser #2, by nysus

by nysus (Parson)
on Apr 15, 2001 at 20:36 UTC ( [id://72671]=perlquestion: print w/replies, xml ) Need Help??

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

This is designed to test your knowledge of how arguments get passed to subroutines.

Instructions for Newbies:

Part A
What do you think the output of the code below is?

Part B
Give an accurate explanation for your answer.

Part C
What happens when you replace "subroutine ();" in line 7 with "&subroutine;"? Why?

Instructions for Non-Newbies:

I'm not going to provide an immediate solution or explanation for this one. Feel free to drop hints and provide general guidance as you see fit.

The Code

1 #!/usr/bin/perl 2 3 use strict; 4 5 @_ = qw(alpha omega); 6 $_ = qw(nothing nothing); 7 subroutine (); 8 9 sub subroutine { 10 print "In the beginning there was $_[0], in the end there will +be $_[1].\n"; 11 }

Edit: chipmunk 2001-04-16

Replies are listed 'Best First'.
Re: NEWBIE Brain Teaser #2, by nysus
by nysus (Parson) on Apr 16, 2001 at 19:51 UTC
    HOW THE CODE WORKS

    Part A
    All you have to know to answer this accurately is how to run a Perl script---just plug and chug the code. You'll see that the program yields "In the beginning there was , in the end there will be ." Kind of profound, no?

    Part B
    Part B takes a little more work. From the output, it appears that nothing---and I mean nothing, not 'nothing'---gets passed to the subroutine. Now take a look at line 7. Notice the empty set of parentheses? You could translate the empty set of parentheses in plain english to mean "nothing will get passed to the subroutine". So then, the answer to why we get what we get in Part A is that nothing is getting passed to the subroutine!

    Some of you might be wondering why we didn't get "alpha" and "omega" in our output. While a quick perusal of perlsub might lead you to believe this would happen, a closer inspection of the perlsub document explains why it doesn't. I'll just tell you: The reason why "alpha" and "omega" aren't passed to the subroutine is that the "@_" array gets locally scoped. This means that the original contents of "@_" (in this case "alpha and "omega) get temporarily placed into storage while a new "@_" appears on the scene to hold the arguments passed to the subroutine. Once the subroutine finishes, the original contents of "@_" get restored (back to "alpha" and "omega"). A good way to understand what is happening is to think about a Hollywood stunt double. As you probably realize, most movies use a stand-in instead of the "real" actors to do anything even slightly dangerous. In programming jargon, the stunt double gets locally scoped into the movie---he/she replaces the original actor to perform the stunt. Once the stunt is over, the original actor will reappear to act out the rest of scene. Applying this to our bit of code, since no arguments are getting passed to the subroutine, our "stunt variable", "@_", will be empty and this is the complete reason why we get what we get in Part A.

    Finally, there may be a question as to what line 6 is doing here. Well, just read what's in the parentheses for the answer: NOTHING and NOTHING. I originally put this line into the code only to add an element of confusion. But to my amazement, and I'm sure even to the amazement of many seasoned Perl programmers, it does come very much into play in Part C. Read on, we're just getting warmed up!

    Part C
    The first thing to understand is that this code will behave differently depending on whether you are using Perl version 5.005 or 5.6. We'll go over both here.

    Version 5.005 Behavior (the version I used)
    In my discussion of "stunt" variables, one thing I neglected to tell you is that it is possible to force the "movie star" variable (the original "@_" array) to perform its own stunt. In other words, you can force the subroutine to use whatever was in "@_" before the subroutine was called. To get a clear understanding of what I mean, do the following:
    ---Change line 7 per the instructions for Part C
    ---For now, delete line 6. We'll come back to it later.
    ---Run the program
    You should see that the output of the code is now, "In the beginning there was alpha, in the end there will be omega." The important thing to note here is that by adding the ampersand and removing the parentheses, you force the subroutine to accept whatever the contents of "@_" were before the subroutine was called. Back to our Hollywood analogy, the movie star is doing his/her own stunts. Now, will you ever be coding anything like this in the real world? Probably not. But remember, this is just a brainteaser...it's meant only to exercise the Perl muscle in your brain and make it stronger. The better you are at thinking about how Perl behaves, the better Perl programmer you'll be...I think. :-)

    Now all we have left is to do is settle the matter of how the code works with line 6 involved. I myself was baffled when I ran the code with the change to line 7 and saw In the beginning there was nothing, in the end there will be nothing. I fully expected line 6 to be ignored (as it was in the original code) and see "alpha" and "omega" in the output. Instead, I created by own Frankenstein and got stumped by my own brainteaser! I'll do my best to explain what's going on. Remember that this applies only to Perl version 5.005.

    To understand this mystery, throw in print "@_"; between lines 6 and 7. Ah ha! We now see that the two elements of "@_" are no longer 'alpha' and 'omega' but are 'nothing' and 'nothing'. From this we can only conclude that line 6, $_ = qw(nothing nothing);, is assigning its list to "@_". But how? Why does assigning something to the "$_" scalar variable in line 6 affect the "@_" array variable? Let's see if we can't isolate the problem further:

    That "$_" variable is a little scary to me. Who knows what built-in magical functions it might harbor? Let's get rid of it. Keep the last change you made (assuming you did) of adding print "@_"; to the code. Now change line 6 from $_ = qw(nothing nothing); to my $hello = qw(nothing nothing);. Run the program. Interesting! We see that our line 7, print "@_"; still gives us 'nothing' and 'nothing'. From this we can deduce that something funny is going on with the qw function. Hang in there, we're almost home!

    This sounds like a case for perldoc to help us solve. So we consult perldoc and get the following on our prime suspect, qw: qw is equivalent to splitting a single-quoted string on whitespace. Ah ha! So qw behaves a little differently than we thought. The qw funtion is really a split function in disguise!!!

    So let's consult the perldoc again and see if we can't find out about our new suspect. And there in the second paragraph of the split function "Description" we find: "If split is used not in list context, split returns the number of fields found and splits into the @_ array....The use of implicit split to @_ is deprecated, however, because it clobbers your subroutine arguments." Well, what do you know? "Clobbering" our subroutine argument sounds exactly like what happened to us!

    So, in essence, what happened is that when we used qw in a scalar context, it called the split function which returned the number '2' and assigned it to our scalar variable. (You can see this for yourself if you print out the contents of "$_".) The split function, used in this scalar context, also rudely dumped its contents into our "@_" variable. And so this is why we see 'nothing' and 'nothing' popping out of our subroutine. Got it?

    Version 5.6 Behavior Version 5.6 makes things a whole lot easier to explain. It appears that the qw function never calls on the split function to do its work. I confirmed this by checking out http://www.perldoc.com/perl5.6/pod/perldelta.html. Indeed, it says the following: The qw// operator is now evaluated at compile time into a true list instead of being replaced with a run time call to split(). This removes the confusing misbehaviour of qw// in scalar context, which had inherited that behaviour from split(). So now, when you run this script under Perl 5.6, you will get:

    "In the beginning there was alpha, in the end there will be omega."

    And there you have it!

Re: NEWBIE Brain Teaser #2, by nysus
by merlyn (Sage) on Apr 15, 2001 at 21:01 UTC

      SPOILER ALERT!

      From my understanding, the existing @_ will be passed by default when you use &subroutine instead. Is there a change in 5.6?

      Edit: chipmunk 2001-04-16

        You are correct that @_ will be passed to a subroutine if you call it without using the parenthesis. This is true of both 5.6 and 5.005 at least.

        However the result of the attempted assignment of a list to $_ differ depending on Perl version, and this DOES affect the output you will see.

      $_ = qw(nothing nothing)
      Translates to:
      1. Under 5.005
      @_ = qw(nothing nothing)
      2. Under 5.6
      No effect on @_;
      Resulting
      call for &subroutine
      1. Under 5.005
      prints "..nothing..nothing"
      2. Under 5.6
      prints "..alpha ..omega"

      Try:

      print qw(1 2 3)[0]
      under 5.005 and 5.6.
Re: NEWBIE Brain Teaser #2, by nysus
by Chady (Priest) on Apr 15, 2001 at 21:14 UTC
    • nothing doesn't mean 'nothing'
    • try to use -w

    He who asks will be a fool for five minutes, but he who doesn't ask will remain a fool for life.
Re: NEWBIE Brain Teaser #2, by nysus
by Ri-Del (Friar) on Apr 16, 2001 at 00:58 UTC
    Instructions for Newbies:
    Part A
    What do you think the output of the code below is?

    I figured that the result would be "In the beginning there
    was nothing, in the end there will be nothing."

    Part B
    Give an accurate explanation for your answer.

    The reason I assumed this, was because I thought that @_ = qw(alpha, omega); assigned the values "alpha" and "omega" to an array called "_". While, $_ = qw(nothing, nothing); ends up assigning the value "nothing" to a scalar variable called "_". Which I think is related somehow to the manner in which we call the array "_". Hmm, perhaps we are actually altering something with the array by assigning a value to the $_ variable? I've just started learning perl and I seem to remember reading something about that.

    Part C What happens when you replace "subroutine ();" in line 7 with "&subroutine;"? Why?

    Well, I had to go look it up, because quite frankly I had no clue whatsoever. However, it appears that by placing an '&' in front of the "subroutine ();" call is really no different than not putting it in front because of the fact that the '&' prefix is considered to be optional. According to the second edition of the camel book, on page 114, it seems that when one names a subroutine, or if one is attempting to get a reference to a subroutine, or if one wishes to "do an indirect subroutine call with a subroutine name or reference using the &$subref() or &{$subref}() constructs..." then the '&' is not optional.

    Another Question: When I attempted to add "-w" and then correct the "Use of uninitialized value in concatenation (.) at brain2.pl line 10." I think I realized that in fact we are using something that really isn't a variable or an array. So um.. I thought maybe @_ and $_ are actualling something else, so that is when I decided to go to the index of my trusty Camel II book. I think I realize where I went wrong and it is related to the error I recieved when I added "-w". Apparently $_ and @_ are actually global variables. But my question is, what exactly are they? Are they the arguments that are passed in when I run the script?

      Well, let's see...for Part A you guessed that the result would be "In the beginning there was nothing, in the end there will be nothing." But is this the result you got when you ran the script?

      If you run the script through Perl, you'll actually get "In the beginning there was , in the end there will be ." But why?

        Yes that is exactly what I got. "In the beginning there was , in the end there will be ." As to the reason, the only thing I thought at first was that as far as I have learned "$_" and "@_" are actually similar to 'argv' in C. Since, we had not passed any parameters to the script nothing was printed out. So I tried that passing two parameters to the script when I ran it. However, as I am sure you know, nothing changed. I have considered that maybe the word 'nothing' is similar to declaring something null, however, I can not find anything to back this up. Does not @_ mean the same thing as @ARGV, that these are the arguments passed to a script? So that if I pass two arguments they will reside in $_[0] and $_[1]? So in effect about the only thing I can conclude at the moment (and I don't particularly like my answer) is that when we state @_ = qw(alpha omega); we are overwriting the first two parameters (if any were passed) and then in the second line, $_ = qw(nothing nothing); setting them both to null. So in effect we set two values, set them to null and then print them out. Am I close?
        You are over complicating some things. First, answer this question and keep your answer as simple as possible:

        How do you pass an argument to a subroutine?

        Yes, there are a few different ways, but there is one way that is most commonly used nowadays and that's the answer I'm looking for.

        Because @_ is localised for the subroutine. Since you called subroutine with no parms, the @_ it's operating from has undef.

        As for &subroutine, @_ is not localised and the existing one is passed in. $_ need not apply outside the sub in any event -- the subroutine deals with @_.

        --
        Me spell chucker work grate. Need grandma chicken.

        Give the man above a see-gar...well maybe half of one.

        You explanation is correct for Part B. But Part C yields "In the beginning there was nothing, in the end there will nothing." (At least it does in my version of Perl 5.005.) I'm not sure I quite follow your explanation for this result.

      As for Part B, which asks "why?"...reading perlsub will give you a better understanding of how to pass variables to a subroutine. If you scope :-) it out very closely you may be able to figure out the answer to Part B. However, if you have a copy of "Learning Perl" by merlyn handy, you may get a friendlier explanation for what this bit of code does.
      I'm going to leave Part C alone for now. It's a bit of a bear.

      I will tell you that "&" is optional only when you call a subroutine with parentheses. For example, subroutine();. In this case, no "&" is needed. But try changing line 7 to plain old "subroutine;" and you will see that the script fails with an error.

Re: NEWBIE Brain Teaser #2, by nysus
by koolade (Pilgrim) on Apr 16, 2001 at 06:58 UTC

    This was a good exercise. My biggest hurdle was figuring out why $_ held the value 2 and @_ contained qw(nothing nothing) rather than qw(alpha omega). After a bit of searching I think I found the answer.

    I tracked down information on the qw operator and found this in the perlop manpage:

    qw// is exactly equivalent to split(' ', q/STRING/); This equivalency means that if used in scalar context, you'll get split's (unfortunate) scalar context behavior...

    So read the documentation on split and found:

    If not in list context, returns the number of fields found and splits into the @_ array.

    So is that pretty much answer? Is there any more magic to it?

      I believe that qw// no longer use split in 5.6. Instead, it's using the comma-operator. As a result, you are just trying to assign a list to a scalar. Thus, $_ will just hold 'nothing' and the answer for part c is "In the beginning there was alpha, in the end there will be omega."

      Disclaimer: I don't have 5.6 installed, so the above is pure speculation. Pls correct me if I'm wrong. : )

        Actually, with version 5.005, the script yields: "In the beginning there was nothing, in the end there will be nothing."
Re: NEWBIE Brain Teaser #2, by nysus
by snafu (Chaplain) on Apr 16, 2001 at 08:22 UTC
    Well, I cheated and ran the script to see what would happen.
    But I will offer a guessed explanation.

    The first script I ran was as is shown above. My output was:

    "In the beginning there was , in the end there will be . "

    B: I believe the output was this way because when the subroutine
    was called via the subroutine(); nothing was passed to the subroutine via the '()' thus there
    were no valid local (to the subroutine) variables called
    '_' therefore nothing being substituted in those slots.

    C: On the other hand when you change the subroutine(); to &subroutine; it printed:

    "In the beginning there was nothing, in the end there will be nothing. "

    The reason this occurred is because when calling a subroutine
    with the '&' the sub automagically "sees" the global scalar
    array named '_'.

    I admit, lexical scope vs dynamic scope is still confusing to me and
    arrays with scope are questionable by me too. I'd be
    interested to know how close I am to being correct.

Re: NEWBIE Brain Teaser #2, by nysus
by jynx (Priest) on Apr 17, 2001 at 04:49 UTC

    You could also have added the following questions, which discuss subroutines and scoping as well:
    4) What happens if you move line 7 to line 12? 5) After question 4, what happens when you do the following (in order) + to line 12? why? a) Remove the parentheses b) Remove the semi-colon c) Precede line 12 with an ampersand (&)
    jynx

    PS please forgive my poor question asking ability...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://72671]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (6)
As of 2024-03-28 19:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found