in reply to Re^13: Why is the execution order of subexpressions undefined?
in thread Why is the execution order of subexpressions undefined?

This node falls below the community's threshold of quality. You may see it by logging in.
  • Comment on Re^14: Why is the execution order of subexpressions undefined?

Replies are listed 'Best First'.
Re^15: Why is the execution order of subexpressions undefined?
by bart (Canon) on Apr 15, 2005 at 08:51 UTC
    Just one word: side effects. (No, that's two words. Whatever. :))

    If your expression contains parts with side effects (changes of "global" variables, reading input from the outside world), then it may depend on execution order. Actually, it only will, if you do read from it more than once. So,

    (scalar <FH>, scalar <FH>)
    depends on execution order, as does
    ($i++, $i++)
    while
    (scalar <FH>, $i++)
    or
    ($i++, $k++)
    do not.

    Expressions with no side effects don't ever depend on execution order. That's one of the basic ideas behind functional programming.

    update: It's not always obvious to see when a statement has side effects. Two examples I just recently thought of are rand and the operators .. and ..., which all have a built-in variable holding their current state.

      You should note that the comma operator has a defined execution order. Its better to use examples with operators other than comma.
Re^15: Why is the execution order of subexpressions undefined?
by Anonymous Monk on Apr 14, 2005 at 21:11 UTC
    This is Anon-a-monk #2 chiming in here, trying to get us back on the same track. The problem of EOD arises whenever side-effects (such as assignment) occur inside your expression. If you eliminate all assignments (read: side-effects) in your expression, you'll be guarenteed not to have an evaluation order dependency. Here's a quick rule-of-thumb: If you are using assignment, make sure the RHS doesn't contain terms that cause an assigment to the same variable on the LHS. Examples...
    $i = 4+$j; #good, no assignments on RHS $i = 4+$j++; #also good, assignment ($j++) doesn't refer to LHS ($i) $i = $i + 1; #good, still no assignments on RHS $i = $i++ #BAD, assignment ($i++) uses variable which occurs on LHS
    For further reading, I recommend Section 3.1.3 of SICP.

      I'd make it more general than that: If, in an expression, you use an operation that changes ("writes" to) some resource, then you should not use that same resource anywhere else in that expression (directly or indirectly).

      $i++ changes $i so don't use $i anywhere else in an expression that uses $i++ (including either side of the assignment operator). If you need to use $i more than once in an expression, then use $i+1 not $i++ nor ++$i and do the assignment / increment of $i's value outside of the expression.

      Another example is that <IN> . <IN> depends on order of evaluation. Each read changes the state of the file handle, impacting what value will be read from it in future.

      Then the next concept is what separates expressions in time. In C, they use the term "sequence points" to describe the points in time across which operations will not move. In Perl, ";" and "," are supposed to create sequence points. But Perl also creates aliases so your example using "," runs into problems because the alias is created in a defined order before a change is made and then a value is pulled from the alias after the change.

      When you get an alias vs. a copy can be quite subtle in Perl. So you shouldn't rely on "," being a sequence point for things that might get aliased (which includes variables and even ++$i, at least in some implementations of Perl).

      Of course, all of this gets rather complicated and isn't obvious so if you'd tried to predefine order of operations you'd probably have been royally screwed when you ran across this wrinkle and then been forced to make Perl make copies in a lot of cases where it doesn't now, making Perl slower.

      - tye        

      Thanks for attempting to answer my questions.

      However, I still see some outstanding issues.

      1. Tied variables.

        Any tied variable used twice in a single expression could have sideeffects.

        As the user of a variable returned by a library call, I have no idea whether the value I am manipulating is tied or not.

        If it is tied, it could have dependancy issues.

        That means that the only way for me to avoid the possibility of EOD, is to never use the same variable twice in a single expression.

        Even something as innocent as:

        my( $x, $y ) = ( $a, $a );

        can produce totally different results depending upon the order execution if $a is a tied variable.

      2. Object references.

        Using an object reference twice in the same expression could involve EOD.

        Again, with EO undefined, the only recourse for teh programmer is to never use an object reference twice in a single expression.

        Again, a silly simple example:

        my( $x, $y ) = ( $o->next, $o->next );

        Should I have to code that as:

        my $x = $o->next; my $y = $o->next;

        to ensure I get the effect I want?

      3. Closures.

        A library function may use closures. Calling the function twice in the same expression could involve EODs.

        Again, the only answer to guarentee that the function will be called in the correct order, is to call it once per expression.

        And the same silly example:

        my( $x, $y ) = ( func(), func() );

        It doesn't matter what func()> does, if there is no guarentee that the two calls to it will be executed in some order, I cannot know what the outcome of that expression will be.

      All three of the restrictions make a mockery of Perl's best feature--that it is a Very High Level Language that allows me to do a lot in each line or statement.

      It means that without diving into the guts of every function, object and module that I use to discover exactly how the code is implemented, I can never use two occurances of a single variable in a single expression. One line will have to exactly one, very low level step and one step only. I might as well write in assembler.

      But the really, really gaulling thing about this, that I've pointed out over and over, and the other anonymonk has chosen to completely ignore, is that the very same set of reasons above, that impose such a burden on writing code that codes a single high-level, algorithmic step per line, are the very same reasons that the Perl cannot possibly derive the slightest efficiency benefit from undefinedness of EO.

      So, arguing about whether it is possible to avoid EOD expressions becomes a complete waste of time, when you realise that there is no good reason not to define an execution order.

      • It isn't hard to do.
      • It doesn't prevent any efficiency gains.

      But if it were defined, it would allow Perl to realise it's potential as a VHLL even further. I believe this to be true for Perl 5, but I realise that it is much too late for that now.

      But for Perl 6, the possibility not only exists, with real benefits for the programmer as an outcome, I believe, as far as I understand Perl 6, that it almost becomes a necessity. Once you consider the potential pifalls of undefined EO when combined with things like hyperoperators, junctions, on-the-fly roles, the ability to create temporary, single instance new classes whose lives extend for a single expression only and that's before you even begin to consider the possibilities that got me interested in the first place, namely threading.

      It simply isn't enough to say "Don't write expressions that could have EODs", because already, it is near impossible to know when even a single, apparently non-lvalue expression could involve sideeffects. And that is even more true of the very complex syntax that will come with Perl 6.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco.
      Rule 1 has a caveat! -- Who broke the cabal?
        You've just rediscovered the fact that shared-state concurrency is a nasty mess. Humans aren't smart enough to use it correctly in anything but the most controlled conditions. Declarative concurrency, on the other hand, is possible to understand. Check into Concepts, Techniques, and Models of Computer Programming. They've also got a wiki
        It simply isn't enough to say "Don't write expression that could have EODs, because already, it is near impossible to know when even a single, apparently non-lvalue expression could involve sideeffects.
        But this is one of the hard problems because of Turing-Completeness/Halting-Problem. What we'd really like to do is outlaw those expressions in the first place. But if the compiler was smart enough to recognize all of those instances, then there are also programs that would cause the compiler to never finish executing :-(

        And, if you explictly define an order of evaluation for everything, then of course you won't gain anything through parallelization (because everything has to be executed sequentially, by definition).

        You should really check into Haskell, no side-effects to screw up things.

        A reply falls below the community's threshold of quality. You may see it by logging in.
Re^15: Why is the execution order of subexpressions undefined?
by Anonymous Monk on Apr 14, 2005 at 21:32 UTC
    Just following up with the original anon-a-monk's original example...
    my $i = 1; sub f {$i += 1; $_[0] * $i} print f(2) + f(3);
    ...to see why this is bad, substitute each call of f() in the print statement with its definition...
    print do{$i += 1; 2 * $i} + do{$i += 1; 3 * $i};
    ...notice that there are two assignments which refer to the same variable on the RHS, which violates our anti-EOD criteria from above.
Re^15: Why is the execution order of subexpressions undefined?
by Anonymous Monk on Apr 14, 2005 at 15:59 UTC
    One final time. Again, I will quote myself. If you keep stating that I fail to provide what I mean with order of evaluation, I must conclude you either refuse to read what I write, or that you don't master the English language.
    But it's easy to describe when an expression has an order of evaluation dependency: whenever an expression has 2 (or more) subexpressions in which it matters which subexpression is evaluated first (it) has a dependency on order of evaluation.