rfc:loop_default

This is an old revision of the document!


loop + default control structure

Introduction

It is often desirable to perform a set of actions if the conditions to enter a loop are not met. This can require a significant amount of boilerplate code if the conditions are complex.

This RFC proposes an optional default control block for loops, that is executed in the event that the loop is not entered.

Proposal

Pre-condition loops (for, foreach and while) will be allowed to have an optional default block (using the existing default keyword) after their loop body, which is executed when the body of the loop is not entered (condition failed, empty array, etc.).

Usage:

while ($cond) {
	// loop body
}
default {
	// did not enter while loop
}
 
for ($i = 0, $j = $k; $i < 4 && $j < 65536; $i++, $j >>= 1) {
	// loop body
}
default {
	// did not enter for loop
}
 
foreach (generator() as $k => $v) {
	// loop body
}
default {
	// did not enter foreach loop
}

This kind of behaviour has been suggested before using the else keyword, however else has several drawbacks:

Not backwards compatible by default due to dangling else

if ($something)
	while ($cond)
		// loop body
else
	// this now belongs to the while loop

Making it backwards compatible leads to inconsistent behaviour

if ($something)
	while ($cond)
		// loop body
	else
		// this still belongs to the if statement

It breaks familiarity with similar behaviour in other languages

while ($cond) {
	// loop body
}
else {
	// In Python this will always execute unless break; is used in the loop body
}

This could be solved by introducing a new keyword, however to maintain backward compatibility as far as possible it is more sane to borrow an existing keyword with a similar semantic meaning, in this case from switch(); if the condition is not met then the default is used.

The intention is to implement this by duplicating loop prologues to avoid the requirement for tracking variables, and keep performance on-par with pre-patch looping.

Pre-patch basic while loop.

line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   ASSIGN                                                   !0, 3
   4     1  >   POST_DEC                                         ~1      !0
         2    > JMPZ                                                     ~1, ->6
   5     3  >   PRINT                                            ~2      'loop'
         4      FREE                                                     ~2
   6     5    > JMP                                                      ->1
         6  > > RETURN                                                   1

Post-patch basic while loop with default block (labels added to help visualise flow)

         # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
         0  >   ASSIGN                                                   !0, 3
cond_1:  1  >   POST_DEC                                         ~1      !0
         2    > JMPZNZ                                   loop            ~1, ->default
cond_2:  3  >   POST_DEC                                         ~1      !0
         4    > JMPZ                                                     ~1, ->nxt_op
loop:    5  >   PRINT                                            ~2      'loop'
         6      FREE                                                     ~2
         7    > JMP                                                      ->cond_2
default: 8  >   PRINT                                            ~3      'default'
         9      FREE                                                     ~3
nxt_op: 10  > > RETURN                                                   1

The key here is that cond_1 uses JMPZNZ to jump straight to loop on the first iteration, but cond_2 is used for all iterations subsequently.

Backward Incompatible Changes

Nothing forseen.

Proposed PHP Version(s)

PHP.next (whether next is 7.0 or 5.7)

RFC Impact

To SAPIs

All SAPIs gain the same functionality

To Existing Extensions

No standard extensions should be affected, only the parser and compiler are modified.

To Opcache

TODO - There may be an impact here, but any patch will review Opcache. This section needs commentary from internals.

Open Issues

None

Unaffected PHP Functionality

break and continue both continue to function sanely.

Both require the loop to be entered to have an effect, which means the default block cannot be executed at the point these constructs are used.

Alternate syntax loops also continue to function as expected with a default: clause that does not alter switch statements.

Loops without bodies also get to have default blocks

Future Scope

There may be an opporunity to support the python style loop+else, but this will require a new keyword to preserve dangling else backward compatibility

Proposed Voting Choices

This is a language change and requires a 2/3 majority in favour of the feature.

Patches and Tests

A proof of concept is being worked on by Leigh

Implementation

TODO

References

Original RFC:

loop_else (by Dmitri Ravazin)

Original discussions in bug tracker:

while {} else {} (by php at bellytime dot com)

Build in foreach else support (by kjarli at gmail dot com)

For... else construct (by jeroenvandenenden at gmail dot com)

Changelog

2014-09-18 - v1.0 - RFC created

rfc/loop_default.1411113578.txt.gz · Last modified: 2017/09/22 13:28 (external edit)