http://qs1969.pair.com?node_id=428251


in reply to Scalar refs, aliasing, and recursion weirdness.

perlfunc's entry on substr says:

If the lvalue returned by substr is used after the EXPR is changed in any way, the behaviour may not be as expected and is subject to change. This caveat includes code such as print(substr($foo,$a,$b)=$bar) or (substr($foo,$a,$b)=$bar)=$fud (where $foo is changed via the substring assignment, and then the substr is used again), or where a substr() is aliased via a foreach loop or passed as a parameter or a reference to it is taken and then the alias, parameter, or deref'd reference either is used after the original EXPR has been changed or is assigned to and then used a second time.

I had to read that last sentence several times to understand it, but I think it covers what your code does: you've taken a reference to a substr, changed the referee string, taken a reference to a substring of that referee, and changed the referee of that. Don't do that.

Here's some code that does what you want:

#!/usr/bin/perl -l use warnings; use strict; sub recurse($); sub recurse($) { my $a = $_[0]; print ">> '$a'"; $a =~ s/ ^ (.) (.+) (.) $ / join '', $1, recurse $2, $3 /ex; print "<< '$a'"; " ( $a ) "; } print "Result: ", recurse 'aaaaaaaaaa';

Markus

Update: diotalevi pointed out that my description of the problem with the OP's code was wrong. I've fixed it.

Replies are listed 'Best First'.
Re^2: Scalar refs, aliasing, and recursion weirdness.
by BrowserUk (Patriarch) on Feb 05, 2005 at 00:29 UTC

    Thanks. I think you are right regarding the re-use of a modified lvalue ref.

    Whilst your code achieves the notionally "desired" output of the testcode I posted, as I said, this is a simplification.

    The real application could be recursing into multiple substrings of the parameter at each level. Where and when the recursion occurs is dependant upon the content of the (sub)string passed and cannot be easily codified into a regex. For various reasons I wish to avoid using the regex engine also.

    That said, I may not have those choices now.


    Examine what is said, not who speaks.
    Silence betokens consent.
    Love the truth but pardon error.
      Still probably not what you are looking for (i.e. no refs), but closer maybe? (at least no regex)
      #!/usr/bin/perl -slw use strict; my $str = 'aaaaaaaaaa'; print recur($str); sub recur { my $s = shift; return "" if length($s)==0; substr($s,1,-1) = recur(substr($s,1,-1)); return "($s)"; }


      -- All code is 100% tested and functional unless otherwise noted.
        Still probably not what you are looking for (i.e. no refs)

        The refs were only a half-arsed attempt to get thing to work when I suspected that the @_ aliasing was responsible for "undo" my changes--a total wrong guess!

        And, your post has moved me along considerably. Your insight of using the same substr as an lvalue into which to assign the return from the recusion--with the same parameters as are used to supply the substring to that level of recursion is (I think) the solution.

        I've put that into the real code and it gets me closer to the desired result. At this stage, the rest of the problem appears to down to my selecting the correct substring--ie. a completely different part of the problem.

        So thankyou. Very much.


        Examine what is said, not who speaks.
        Silence betokens consent.
        Love the truth but pardon error.
Re^2: Scalar refs, aliasing, and recursion weirdness.
by diotalevi (Canon) on Feb 05, 2005 at 16:12 UTC

    Marcus, you are incorrect when you summarize the documentation as noting that modifying a reference taken to a substring is forbidden. That's wrong. You can do that to a reference once, but not two or more times.

    $_ = 'aaaaaaaaaa'; $_ = \ $_; $$_ =~ s/ ... / ... /; # This is ok. $$_ =~ s/ ... / ... /; # This is not.

    Modifying substring lvalues is typically safer if you don't do something to persist the lvalue beyond the length of a statement - you're less likely to find yourself accidentally modifying it more than once. The following expression is a highly useful form of lvalue substrings and one that people should be more aware of. It would not be available if lvalue substrings were not available.

    substr( ... ) =~ s/ ... / ... /g
      diotalevi writes:

      Marcus, you are incorrect when you summarize the documentation as noting that modifying a reference taken to a substring is forbidden. That's wrong.

      You're right. I expressed myself so badly that what I wrote was factually wrong. I'll update my original response. Thanks for pointing out my mistake.

      Incidentally, though, as long as no substr is involved, you can run as many substitutions as you wish on an indirected reference. Hence:

      [~/perl/monks]$ ./test abc [~/perl/monks]$ cat test #!/usr/bin/perl -l use warnings; use strict; my $a = 'aaa'; $_ = \$a; $$_ =~ s/aa/ab/; $$_ =~ s/ba/bc/; print $$_; [~/perl/monks]$

      That $_ = \$_ construction is interesting. It yields a variable for which $_ == $$_ == $$$_, etc. No matter how many times you indirect it, the type and value of $_ don't change. There must be some code that breaks when you do that!

      Markus

      This proved to be the final piece in my puzzle. Instead of passing an lvalue ref to a selected substring into deeper levels of recursion, I have to pass the (aliased) target string, and the start/end pair of teh selectd substring. The deeper level can then use substr to modify the appropriate bit of the target without falling into the trap of re-using a modified lvalue ref.

      Passing the three salient pieces of information around separately is less convenient that doing so nicely encapsulated in the lvalue ref, and it forces me to do the math of combining the start-end pair passed at a given level with the start-end pair selected from within it, before passing them in deeper.

      It also forces me to manipulate the start/end pair I receive to account for any shrinkage or growth of the selection made at this level or any deeper levels called from this level--within my caller.

      Perfectly doable at this level, but it would also be perfectly doable--and more efficient and convenient--if Perl did that for me. I've no doubt that it could be done by Perl given the interest of someone with sufficient tuits at that level.

      Perhaps the most diconserting thing about this whole thread is that Perl is silently converting an lvalue ref to a normal scalar when a second modification through it is attempted, and thus discarding the changes made by that second change!

      That ought to be a red-flag. Maybe it should be the subject of a perlbug?


      Examine what is said, not who speaks.
      Silence betokens consent.
      Love the truth but pardon error.

      It seems that someone already did offer a patch that would perpetuate the lvalueness of an LVALUE ref when it is modifed. Indeed, it was done in response to a perlbug diotalevi raised following an earlier exposition of mine on the subject here.

      However, it would appear that patch was rejected in favour of the quick fix of (silently) converting a modified LVALUE to a mortal SV if a further attempt to modify it was encountered.


      Examine what is said, not who speaks.
      Silence betokens consent.
      Love the truth but pardon error.