rfc:generators

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:generators [2012/08/25 15:58] – New rewind() behavior nikicrfc:generators [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====== Request for Comments: Generators ====== ====== Request for Comments: Generators ======
   * Date: 2012-06-05   * Date: 2012-06-05
-  * Author: Niktia Popov <nikic@php.net> +  * Author: Nikita Popov <nikic@php.net> 
-  * Status: Under Discussion+  * Status: Implemented
  
 ===== Introduction ===== ===== Introduction =====
Line 189: Line 189:
          
     mixed send(mixed $value);     mixed send(mixed $value);
-    void close();+    mixed throw(Exception $exception);
 } }
 </code> </code>
Line 223: Line 223:
 Apart from the above the ''Generator'' methods behave as follows: Apart from the above the ''Generator'' methods behave as follows:
  
-  * ''rewind'': Throws an exception is the generator is currently after the first yield. (More in the "Rewinding a generator" section.)+  * ''rewind'': Throws an exception if the generator is currently after the first yield. (More in the "Rewinding a generator" section.)
   * ''valid'': Returns ''false'' if the generator has been closed, ''true'' otherwise. (More in the "Closing a generator" section.)   * ''valid'': Returns ''false'' if the generator has been closed, ''true'' otherwise. (More in the "Closing a generator" section.)
   * ''current'': Returns whatever was passed to ''yield'' or ''null'' if nothing was passed or the generator is already closed.   * ''current'': Returns whatever was passed to ''yield'' or ''null'' if nothing was passed or the generator is already closed.
Line 229: Line 229:
   * ''next'': Resumes the generator (unless the generator is already closed).   * ''next'': Resumes the generator (unless the generator is already closed).
   * ''send'': Sets the return value of the ''yield'' expression and resumes the generator (unless the generator is already closed). (More in the "Sending values" section.)   * ''send'': Sets the return value of the ''yield'' expression and resumes the generator (unless the generator is already closed). (More in the "Sending values" section.)
-  * ''close'': Closes the generator. (More in the "Closing a generator" section.)+  * ''throw'': Throws an exception at the current suspension point in the generator. (More in the "Throwing into the generator" section.)
  
 ==== Yield syntax ==== ==== Yield syntax ====
Line 394: Line 394:
 $logger->send('Foo'); $logger->send('Foo');
 $logger->send('Bar'); $logger->send('Bar');
 +</code>
 +
 +==== Throwing into the generator ====
 +
 +Exceptions can be thrown into the generator using the ''Generator::throw()'' method. This will throw an exception in the generator's execution
 +context and then resume the generator. It is roughly equivalent to replacing the current ''yield'' expression with a ''throw'' statement and
 +resuming then. If the generator is already closed the exception will be thrown in the callers context instead (which is equivalent to replacing
 +the ''throw()'' call with a ''throw'' statement). The ''throw()'' method will return the next yielded value (if the exception is caught and no
 +other exception is thrown).
 +
 +An example of the functionality:
 +
 +<code php>
 +function gen() {
 +    echo "Foo\n";
 +    try {
 +        yield;
 +    } catch (Exception $e) {
 +        echo "Exception: {$e->getMessage()}\n";
 +    }
 +    echo "Bar\n";
 +}
 +
 +$gen = gen();
 +$gen->rewind();                     // echos "Foo"
 +$gen->throw(new Exception('Test')); // echos "Exception: Test"
 +                                    // and "Bar"
 </code> </code>
  
Line 432: Line 459:
 ==== Cloning a generator ==== ==== Cloning a generator ====
  
-Generators can be cloned, thus leaving two independent ''Generator'' objects with the same stateThis behavior can for example be +Generators cannot be cloned.
-used to create a ''rewindable'' function, which would make the passed generator rewindable:+
  
-<code php> +Support for cloning was included in the initial versionbut removed in PHP 5.5 Beta 3 due to implementational difficulties, unclear semantics and no particularly convincing use cases.
-class RewindableGenerator implements Iterator { +
-    protected $original; +
-    protected $current; +
-     +
-    public function __construct(Generator $generator) { +
-        $this->original = $generator; +
-        $this->current = null; +
-    } +
-     +
-    public function rewind() { +
-        if ($this->current) { $this->current->close();+
-        $this->current = clone $this->original; +
-        $this->current->rewind(); +
-    } +
-     +
-    public function valid() { +
-        if (!$this->current) { $this->current = clone $this->original;+
-        return $this->current->valid(); +
-    } +
-     +
-    public function current() { +
-        if (!$this->current) { $this->current = clone $this->original;+
-        return $this->current->current(); +
-    } +
-     +
-    public function key() { +
-        if (!$this->current) { $this->current = clone $this->original;+
-        return $this->current->key(); +
-    } +
-     +
-    public function next() { +
-        if (!$this->current) { $this->current = clone $this->original;+
-        $this->current->next(); +
-    } +
-     +
-    public function send($value) { +
-        if (!$this->current) { $this->current = clone $this->original;+
-        return $this->current->send($value); +
-    } +
-     +
-    public function close() { +
-        $this->original->close(); +
-        if ($this->current) { +
-            $this->current->close(); +
-        } +
-    } +
-+
- +
-function rewindable(Generator $generator) { +
-    return new RewindableGenerator($generator); +
-+
-</code> +
- +
-It can be then used as follows: +
- +
-<code php> +
-function xrange($start, $end, $step = 1) { +
-    for ($i = $start; $i <= $end; $i += $step) { +
-        yield $i; +
-    } +
-+
- +
-$range = rewindable(xrange(0, 5)); +
-foreach ($range as $i) { +
-    echo $i, "\n"; +
-+
-foreach ($range as $i) { +
-    echo $i, "\n"; +
-+
-</code> +
- +
-This will correctly output the 0..5 range twice.+
  
 ==== Closing a generator ==== ==== Closing a generator ====
Line 514: Line 468:
 ''valid'' will return ''false'' and both ''current'' and ''key'' will return ''null''. ''valid'' will return ''false'' and both ''current'' and ''key'' will return ''null''.
  
-A generator can be closed in three ways:+A generator can be closed in two ways:
  
-  * Explicitly calling the ''Generator::close'' method. This can be useful if you want to free used memory after you don't need the generator anymore. 
-  * Removing all references to the generator object. In this case the generator will be closed as part of the garbage collection process. 
   * Reaching a ''return'' statement (or the end of the function) in a generator or throwing an exception from it (without catching it inside the generator).   * Reaching a ''return'' statement (or the end of the function) in a generator or throwing an exception from it (without catching it inside the generator).
 +  * Removing all references to the generator object. In this case the generator will be closed as part of the garbage collection process.
 +
 +If the generator contains (relevant) ''finally'' blocks those will be run. If the generator is force-closed (i.e. by removing all references) then it is not
 +allowed to use ''yield'' in the ''finally'' clause (a fatal error will be thrown). In all other cases ''yield'' is allowed in ''finally'' blocks.
  
 The following resources are destructed while closing a generator: The following resources are destructed while closing a generator:
Line 533: Line 489:
 Currently it can happen that temporary variables are not cleaned up properly in edge-case situations. Exceptions are also subject to Currently it can happen that temporary variables are not cleaned up properly in edge-case situations. Exceptions are also subject to
 this problem: https://bugs.php.net/bug.php?id=62210. If that bug could be fixed for exceptions, then it would also be fixed for generators. this problem: https://bugs.php.net/bug.php?id=62210. If that bug could be fixed for exceptions, then it would also be fixed for generators.
- 
-A generator cannot be closed while it is running (e.g. if ''$generator->closed()'' is called within the generator function). In this case and ''E_WARNING'' 
-is thrown and the call is ignored. 
- 
-In Python generators are closed by throwing a ''GeneratorExit'' exception into them. This is *not done* in the PHP implementation. 
- 
-The exception gives the generator function a chance to clean up any resources it uses: 
- 
-<code php> 
-function gen() { 
-    $lock = getLock(); 
-    try { 
-        // do some stuff 
-    } catch (GeneratorExitException $e) { 
-        releaseLock($lock); 
-        throw $e; 
-    } 
-    releaseLock($lock); 
-} 
-</code> 
- 
-As you can see the exception allows the generator to clean up. Also note that the exception has to be rethrown. This way it is ensured that 
-the generator does not simply resume execution, but really does terminate. 
- 
-As PHP does not support ''finally'' blocks which have strict must-run semantics and there are other simple ways to enforce resource cleanup 
-(e.g. destructors) this behavior is not implemented in PHP. 
  
 ==== Error conditions ==== ==== Error conditions ====
Line 564: Line 494:
 This is a list of generators-related error conditions: This is a list of generators-related error conditions:
  
 +  * Using ''yield'' outside a function: ''E_COMPILE_ERROR''
 +  * Using ''return'' with a value inside a generator: ''E_COMPILE_ERROR''
   * Manual construction of ''Generator'' class: ''E_RECOVERABLE_ERROR'' (analogous to ''Closure'' behavior)   * Manual construction of ''Generator'' class: ''E_RECOVERABLE_ERROR'' (analogous to ''Closure'' behavior)
-  * Closing a generator while it is running: ''E_WARNING'' 
   * Yielding a key that isn't an integer or a key: ''E_ERROR'' (this is just a placeholder until Etienne's arbitrary-keys patch lands)   * Yielding a key that isn't an integer or a key: ''E_ERROR'' (this is just a placeholder until Etienne's arbitrary-keys patch lands)
-  * Trying to iterate a non-ref generator by-ref: ''E_ERROR'' +  * Trying to iterate a non-ref generator by-ref: ''Exception'' 
-  * Using ''yield'' outside a function: ''E_COMPILE_ERROR'' +  * Trying to traverse an already closed generator: ''Exception'' 
-  * Using ''return'' with a value inside a generator: ''E_COMPILE_ERROR''+  * Trying to rewind a generator after the first yield: ''Exception''
   * Yielding a temp/const value by-ref: ''E_NOTICE'' (analogous to ''return'' behavior)   * Yielding a temp/const value by-ref: ''E_NOTICE'' (analogous to ''return'' behavior)
   * Yielding a string offset by-ref: ''E_ERROR'' (analogous to ''return'' behavior)   * Yielding a string offset by-ref: ''E_ERROR'' (analogous to ''return'' behavior)
Line 575: Line 506:
  
 This list might not be exhaustive. This list might not be exhaustive.
- 
-===== Patch ===== 
- 
-A working, but not yet complete implementation can be found at https://github.com/nikic/php-src/tree/addGeneratorsSupport. 
  
 ===== Performance ===== ===== Performance =====
Line 692: Line 619:
 So the function-like ''yield($value)'' syntax would optimize a very rare use case (namely ''$recv = yield($send);''), at the same time making the common use cases less clear. So the function-like ''yield($value)'' syntax would optimize a very rare use case (namely ''$recv = yield($send);''), at the same time making the common use cases less clear.
  
-===== TODO =====+===== Patch ===== 
 + 
 +The current implementation can be found in this branch: https://github.com/nikic/php-src/tree/addGeneratorsSupport. 
 + 
 +I also created a PR so that the diff can be viewed more easily: https://github.com/php/php-src/pull/177 
 + 
 +===== Vote =====
  
-  * Decide on whether to implement the ''Generator::throw'' method +<doodle title="Should generators be merged into master?" auth="nikic" voteType="single" closed="true"> 
-  Implement ''yield*'' expression and generator return values+   * Yes 
 +   No 
 +</doodle>
  
 ===== Further resources ===== ===== Further resources =====
rfc/generators.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1