Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Pass array, then clear

by Anonymous Monk
on Jan 15, 2018 at 21:13 UTC ( [id://1207313]=perlquestion: print w/replies, xml ) Need Help??

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

sub clear_it { my($data) = @_; $data = []; #@$data = (); } my $data = ['a','b']; &clear_it($data); print "size = ",($#$data + 1),"\n";

This outputs "size = 2". If I use the second way of clearing the contents of the array (commented out), then that works as expected "size = 0".

This seems like a perl idiosyncrasy. Is there a simple explanation for this?

Replies are listed 'Best First'.
Re: Pass array, then clear
by huck (Prior) on Jan 15, 2018 at 21:26 UTC

    Inside clear_it $data is a local variable that you assign a reference to from the parm stack. the uncommented version then assigns a reference to a empty hash to that local variable. As the subroutine exits the internal $data variable containing the reference is destroyed.
    The commented version uses the reference contained in $data as an address to an array and assigns the empty array to that reference

      Thank you for the explanation. Do you think this is intuitive behavior when dealing with references? I'm kind of bummed about it. I'm concerned that this behavior deserves an explanation somewhere. I'm sure it's in the documentation somewhere, but I did not see anything about this in the Perl references documentation.

      Maybe everyone thinks the current behavior is perfect, in that case I need to adjust my thinking.
        Do you think this is intuitive behavior ...? I'm concerned that this behavior deserves an explanation somewhere.

        No, it's not intuitive; however, this is pretty much the way references work in any computer language. Maybe see reference, perlref, perlreftut. Perhaps also take a look at the Monastery Tutorials articles in References, and maybe also at Coping with Scoping and other articles in Variables and Scoping.

        (And yes, you need to adjust your thinking :) (Update: And yes, references and aliasing have bitten me, too — many times, many, many times...)


        Give a man a fish:  <%-{-{-{-<

        I will admit i have gotten bit by it before, but as soon as i realize i know what to fix. It is the same when

        for my $var (@array) { $var++; }
        ends up modifying the array even tho i said my $var.

Re: Pass array, then clear
by Laurent_R (Canon) on Jan 15, 2018 at 23:41 UTC
    When you write:
    my($data) = @_;
    you're creating a new variable that happens to contain an array ref defined in the caller. Then you simply give another value (namely a reference to an empty array) to that variable. There should not be any reason why this would modify the original array ref.

    Just as if you say:

    my $c = 5; my $d = $c; $d = 6;
    this will not change the value of $c.

    In the commented out version, something else is happening: @$data is actually the array referred to by $data, so you are not modifying the local variable just created in the sub, but the value (the anonymous array referred to by the array ref) $data refers to.

Re: Pass array, then clear
by AnomalousMonk (Archbishop) on Jan 15, 2018 at 23:19 UTC
    Is there a simple explanation for this?

    References be tricky, so I don't know how "simple" you will find this, but however...

    c:\@Work\Perl\monks>perl -wMstrict -le "use Data::Dump qw(pp); ;; sub clear_it { my($data) = @_; print 'in clear_it(): A: ref address: ', $data; $data = []; print 'in clear_it(): B: ref address: ', $data; } ;; my $data = ['a','b']; print 'in main: X: ref address: ', $data; ;; clear_it($data); print 'in main: Y: ref address: ', $data; print 'in main: Z: ref content: ', pp $data; " in main: X: ref address: ARRAY(0x15c6f3c) in clear_it(): A: ref address: ARRAY(0x15c6f3c) in clear_it(): B: ref address: ARRAY(0x15c7074) in main: Y: ref address: ARRAY(0x15c6f3c) in main: Z: ref content: ["a", "b"]
    In this first example, the anonymous reference address created in main and assigned to $data in that scope is passed to a separate variable in the scope of clear_it(), and that separate variable is assigned another anonymous reference address created therein. Notice how the the reference addresses change from point A to point B, yet are the same at points X, A and Y. After a new reference address is assigned to $data within clear_it(), whatever is done to the referenced contents (the referent) of $data inside of clear_it() can have no effect on the referent of the separate $data variable in main.

    c:\@Work\Perl\monks>perl -wMstrict -le "use Data::Dump qw(pp); ;; sub clear_it { my($data) = @_; print 'in clear_it(): A: ref address: ', $data; @$data = (); print 'in clear_it(): B: ref address: ', $data; } ;; my $data = ['a','b']; print 'in main: X: ref address: ', $data; ;; clear_it($data); print 'in main: Y: ref address: ', $data; print 'in main: Z: ref content: ', pp $data; " in main: X: ref address: ARRAY(0x1846f3c) in clear_it(): A: ref address: ARRAY(0x1846f3c) in clear_it(): B: ref address: ARRAY(0x1846f3c) in main: Y: ref address: ARRAY(0x1846f3c) in main: Z: ref content: []
    In this example, the reference address of an anonymous array in main is again passed to clear_it(), but this time an operation is performed by reference on the referent of the original reference, specifically, assigning it the empty list. Notice that the reference addresses at points X, A, B and Y are all the same.

    This seems like a perl idiosyncrasy.

    This is essentially the way references work in any language.


    Give a man a fish:  <%-{-{-{-<

Re: Pass array, then clear
by pwagyi (Monk) on Jan 16, 2018 at 02:13 UTC

    If you have knowledge with pointer (C/C++) it is pretty much the same in Perl.

    sub clear { my ($ref) = @_; $ref = []; # now ref is made to point to new object (array) @$ref = (); # what ref points to is modified }

    $ref is a pointer(reference) variable, it stores only address(so as to speak). So when $ref is assigned to point to another object, subroutine caller does not see the change. On the other hand, if you assign what $ref points to (deference), the change is reflected in caller.

Re: Pass array, then clear
by ikegami (Patriarch) on Jan 16, 2018 at 16:35 UTC

    my creates a new variable, to which you assign a reference to a newly-created, empty array. Why would that affect the caller? You never changed the referenced array; you only changed a sub-scoped scalar.

      I think my problem with all of this was a misunderstanding of the mechanics of calls to subroutines. I did not suspect that the subroutine made a copy of the memory address. I figured that by passing in a reference, then whatever I did to the reference would reflect the new state of the reference. I don't think this was an unreasonable expectation. If the mechanics were different, well it would be different. Suppose the variable passing used some sort of "slot based" mechanism where the caller/sub could agree on variables (references) in and their value (slot) on return? That could work. Maybe it's dumb, but I don't think it's as off the wall as expecting houses to be empty given a new key:)
        I did not suspect that the subroutine made a copy of the memory address

        It doesn't. You do, with the my($data) = @_; snippet. If you directly manipulate the argument, it works as you expect:

        sub clear_it { $_[0] = []; }

        Because you're then working on the original reference, not on the copy you made.

        I did not suspect that the subroutine made a copy of the memory address.

        It doesn't (though you did in the first line of the sub).

        I figured that by passing in a reference, then whatever I did to the reference would reflect the new state of the reference.

        You didn't do anything to the reference. (You changed the value of $data.)

        Also, there isn't really anything you can do to a reference. You can't change a reference any more than you can change 5.

        Suppose the variable passing used some sort of "slot based" mechanism where the caller/sub could agree on variables (references) in and their value (slot) on return?

        That is what happens. You didn't change any of the slots (elements of @_).

        I did not suspect that the subroutine made a copy of the memory address.

        ikegami++ just responded excellently, but I liked the earlier house key analogy, so want to expand on that.

        $outside_data = ['a','b']; clear_it($outside_data); sub clear_it {
        Grab a key unlocking a house containing two rooms; one room contains 'a', the other contains 'b'. Put that key in pocket#0, with pockets#1..n all empty
        my($data)=@_;Grab the contents of pocket#0 (the key), create a duplicate, and put that duplicate in my pants pocket that I've named $data; ignore the contents of all other coat pockets (for now).
        $data = [];Create a new key to an empty house. I want to put the new key into my pants pocket named $data, but that pants pocket was already full. Thus, empty the pocket and lose track of the duplicate key that I used to have in there (but I've still got the original key in my coat pocket#0). Now that my $data pocket is empty, it has room to hold the new key, so I put that key in.
        }At this point, I actually empty the pants pockets and throw away the key to the new house. At some point, the perl garbage collectors will see a house with no keys, and demolish it, freeing up the property for future use. We don't really notice, because the garbage collectors work at night when no one is watching, and we're all waiting with baited breath to look inside a house.
        print "size = ", scalar(@$outside_data)Take the key out of my coat pocket#0, unlock the house, and look at how many rooms are in the house it unlocks. We see 2 rooms, because it's still the original key, and we've never done anything to the inside of the original house.

        alternately:

        $outside_data = ['a','b']; clear_it($outside_data); sub clear_it {
        (same as above)
        my($data)=@_;(same as above)
        @$data = ...Use the duplicate key that's in my $data pocket to unlock the door to the house.
        ... = ();Once inside the house, demolish everything inside that house and throw out all the rubble. It's the same house, it's just now empty inside. Any key that fits the house will still unlock the house.
        }As before, I still throw away the $data key; it was a duplicate, and I still have the key in my coat pocket#0.
        print "size = ", scalar(@$outside_data)Take the key out of my coat pocket#0, unlock the house, and look inside. We see the now-empty house, so it will say 0, because it's still the original house, which we "renovated".

        I hope this helps paint a mental picture of what's going on.

Re: Pass array, then clear
by BillKSmith (Monsignor) on Jan 16, 2018 at 04:39 UTC
    I find that it is almost always a bad idea to allow a subroutine to modify its arguments. You may or may not accept theoretical arguments against it, but the fact that so many people admit that it is confusing is all the reason that I need.
    Bill
      The fact, that the same concept can be found in several different languages, seems to indicate reasonably sizable group of developers would not agree with your opinion.
        Allowing a subroutine to modify its argument is probably necessary in some cases to keep or enhance the expressive power of the language. And, sometimes, it is necessary to use that feature, just as it is sometimes (but rarely) necessary to use some magical constructs of the language. In most modern languages, however, the default is not to allow that (for example by using a call by value evaluation strategy); and you usually need a special syntax to modify the function arguments (such as, depending on the language, using pointers or references, or by specifying explicitly that the subroutine argument should be "read and write", rather than the default read only).

        The fact that you can modify a function argument within the function (provided you really know what you're doing) does not mean, however, that you should do it or that it is a good practice to do so commonly. I do not know whether this is by itself a sufficient reason not to modify the subroutine arguments, but I agree with BillKSmith that most people (including myself) think is is usually a bad idea to do so. Or that, at least, it tends to be confusing and should be avoided when possible.

        Having said that, I am certainly not dogmatic on that question, and I prefer to be pragmatic. I certainly usually tend not to modify my subroutines arguments, even though I sometimes find it useful or even necessary to do so (which can be the case for totally different reasons, such as performance or memory footprint, for example).

Re: Pass array, then clear
by Anonymous Monk on Jan 16, 2018 at 15:46 UTC
    Thank you for all for your comments and explanations. I suspected what was happening and it is good to get clarification. Also, it is insightful knowing that this behavior is typical of programming languages.

      Not just in programming languages. If you have a key to a house in your hand, and someone replaces that key with the key to an empty house, the original house hasn't magically become empty.

Re: Pass array, then clear
by Anonymous Monk on Jan 16, 2018 at 14:22 UTC
    One difficulty inherent in the design of the Perl language is that there is no mandatory declaration in the syntax of the language of what parameters a function takes, and whether the parameter should be passed by-value or by-reference. The statement $a = 2; can work in several different ways, and exactly how it works is determined at runtime. In traditional languages, the compiler would generate different code depending on how a parameter is to be passed. In Perl, "reference" is a data-type. And there is really nothing to be done about it: that's just how this language works.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (6)
As of 2024-03-28 14:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found