rfc:comprehensions
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:comprehensions [2019/03/10 21:22] – created crell | rfc:comprehensions [2019/03/11 11:53] – fix typos in code nikic | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Generator comprehensions ====== | ====== PHP RFC: Generator comprehensions ====== | ||
- | * Version: 0.1 | + | |
- | * Date: 2019-03-10 | + | * Date: 2019-03-10 |
- | * Author: Larry Garfield, larry@garfieldtech.com | + | * Author: Larry Garfield, larry@garfieldtech.com |
- | * Status: Draft | + | * Status: Draft |
- | * First published at: http:// | + | * First published at: http:// |
===== Introduction ===== | ===== Introduction ===== | ||
Line 31: | Line 31: | ||
</ | </ | ||
- | In both cases, '%%'$gen%%'' | + | In both cases, |
===== Proposal ===== | ===== Proposal ===== | ||
Line 39: | Line 39: | ||
The general form of a comprehension is: | The general form of a comprehension is: | ||
+ | < | ||
' | ' | ||
+ | </ | ||
That is, one or more for-if clauses in which the if statement is optional, optionally followed by a '' | That is, one or more for-if clauses in which the if statement is optional, optionally followed by a '' | ||
Line 75: | Line 77: | ||
A comprehension is whitespace insensitive. It may be broken out to multiple lines if it aids readability with no semantic impact. | A comprehension is whitespace insensitive. It may be broken out to multiple lines if it aids readability with no semantic impact. | ||
- | The following examples show a comprehension and the equivalent inline generator. | + | The following examples show a comprehension and the equivalent inline generator. |
<code php> | <code php> | ||
Line 123: | Line 125: | ||
]; | ]; | ||
- | // Whitespace is irrelevant, so breaking it out like this is totally fine if it aids readability. | + | // Whitespace is irrelevant, so breaking it |
+ | // out like this is totally fine if it aids readability. | ||
$result = [for $table as $num => $row if $num %2 ==0 | $result = [for $table as $num => $row if $num %2 ==0 | ||
for $row as $col => $value if $col >= 3 | for $row as $col => $value if $col >= 3 | ||
Line 157: | Line 160: | ||
- In context the for is unambiguously being used in a foreach-style way, thus there is no confusion. | - In context the for is unambiguously being used in a foreach-style way, thus there is no confusion. | ||
- | - The '"%%for%%'' | + | - The ''%%for%%'' |
- The point of comprehensions is a compact yet expressive syntax. | - The point of comprehensions is a compact yet expressive syntax. | ||
Line 199: | Line 202: | ||
The common default "is truth-y" | The common default "is truth-y" | ||
+ | <code php> | ||
$result = [for $list as $x if $x]; | $result = [for $list as $x if $x]; | ||
+ | </ | ||
==== array_map() ==== | ==== array_map() ==== | ||
Line 207: | Line 212: | ||
$result = array_map(function ($x) { | $result = array_map(function ($x) { | ||
- | $x * 2; | + | |
}, $list); | }, $list); | ||
Line 225: | Line 230: | ||
$list = array_combine(range(' | $list = array_combine(range(' | ||
- | // array_map() itself cannot produce an array with dynamically defined keys so is omitted. | + | // array_map() itself cannot produce an array |
+ | //with dynamically defined keys so is omitted. | ||
$result = (function() use ($list) { | $result = (function() use ($list) { | ||
Line 239: | Line 245: | ||
<code php> | <code php> | ||
- | $list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | + | $list = range(1, 10); |
- | // In practice you'd almost always just use a foreach() rather than this monstrosity, | + | // In practice you'd almost always just use a |
- | $result = array_filter(array_map(function ($x) { | + | // foreach() rather than this monstrosity, |
- | $x * 2; | + | // but I include it for completeness. |
- | }, $list), | + | $result = array_map(function($x) { |
+ | | ||
+ | }, array_filter(function() { | ||
return $x % 2; | return $x % 2; | ||
- | }); | + | }, $list)); |
$result = (function() use ($list) { | $result = (function() use ($list) { | ||
foreach ($list as $x) { | foreach ($list as $x) { | ||
- | | + | |
yield $x * 2; | yield $x * 2; | ||
} | } | ||
Line 276: | Line 284: | ||
</ | </ | ||
- | Because a generator implements Iterator, we can call '"%%current()%%'' | + | Because a generator implements Iterator, we can call ''%%current()%%'' |
==== any() ==== | ==== any() ==== | ||
Line 330: | Line 338: | ||
Numerous languages include a comprehension syntax of some form (https:// | Numerous languages include a comprehension syntax of some form (https:// | ||
- | Two of the languages PHP developers are most likely to also use, JavaScript and Python, feature a very similar syntax. | + | The syntax |
- | + | ||
- | Note that in Python 2.x list comprehensions produce a complete list. In Python 3.x they produce a generator that will, in turn, produce a complete list. That change has been a source of incompatibility between Python 2.x and 3.x code. This RFC proposes using generators exclusively for comprehensions. | + | |
If a more terse syntax that is still lexer-friendly can be proposed that may be adopted instead of the syntax proposed here. | If a more terse syntax that is still lexer-friendly can be proposed that may be adopted instead of the syntax proposed here. | ||
+ | |||
+ | Note that in Python 2.x list comprehensions produce a complete list. In Python 3.x they produce a generator that will, in turn, produce a complete list. That change has been a source of incompatibility between Python 2.x and 3.x code. This RFC proposes using generators exclusively for comprehensions. | ||
===== Comparison to other proposals ===== | ===== Comparison to other proposals ===== | ||
- | The "sort lambda" | + | The "short lambda" |
<code php> | <code php> | ||
- | $result = array_filter(array_map(function ($x) { | + | $result = array_map(function($x) { |
- | $x * 2; | + | |
- | }, $list), | + | }, array_filter(function() { |
return $x % 2; | return $x % 2; | ||
- | }); | + | }, $list)); |
$result = [for $list as $x if $x % 2 yield $x * 2]; | $result = [for $list as $x if $x % 2 yield $x * 2]; | ||
Line 351: | Line 359: | ||
The arrow function equivalent would be: | The arrow function equivalent would be: | ||
+ | Which, while unquestionably an improvement over the array_map/ | ||
<code php> | <code php> | ||
Line 359: | Line 368: | ||
</ | </ | ||
- | Which, while unquestionably | + | Or potentially: |
+ | |||
+ | <code php> | ||
+ | $result = (fn() => foreach($list as $x) if ($x % 2) yield $x * 2)(); | ||
+ | </ | ||
+ | |||
+ | Either is definitely | ||
That said, there are ample other cases where arrow functions would be useful so the adoption of this RFC should in no way be seen to detract from their benefit. | That said, there are ample other cases where arrow functions would be useful so the adoption of this RFC should in no way be seen to detract from their benefit. | ||
Line 384: | Line 399: | ||
$gen = [for $array as $x : int]; | $gen = [for $array as $x : int]; | ||
foreach ($gen as $val) { | foreach ($gen as $val) { | ||
- | // A TypeError would be thrown on the 3rd value, as it's not an int. | + | // A TypeError would be thrown on the 3rd value, |
+ | // as it's not an int. | ||
} | } | ||
</ | </ | ||
Line 395: | Line 411: | ||
$run = [for $products as $p yield save($p)]; | $run = [for $products as $p yield save($p)]; | ||
- | // iterator_to_array() will result in an array of return values fro save_entity(). Depending on the data set this could be quite large, and must be allocated even if not saved. | + | // iterator_to_array() will result in an array of return |
+ | // values fro save_entity(). Depending on the data | ||
+ | // set this could be quite large, and must be allocated | ||
+ | // even if not saved. | ||
iterator_to_array($run); | iterator_to_array($run); | ||
- | // An empty foreach() will simply discard the return values, but is rather clumsy. | + | // An empty foreach() will simply discard the return values, |
+ | // but is rather clumsy. | ||
foreach ($run as $val); | foreach ($run as $val); | ||
</ | </ | ||
It would be preferable to introduce a new function or language construct that can take an arbitrary generator and "run it out", discarding the results. | It would be preferable to introduce a new function or language construct that can take an arbitrary generator and "run it out", discarding the results. | ||
+ | |||
+ | ===== Implementation ===== | ||
+ | |||
+ | Sara Golemon has written a proof of concept that demonstrates an approximate implementation: | ||
+ | |||
+ | https:// | ||
+ | |||
+ | It is currently incomplete as it lacks auto-capture and requires an explicit '' | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
Line 411: | Line 439: | ||
PHP 7.4 | PHP 7.4 | ||
- | |||
rfc/comprehensions.txt · Last modified: 2019/04/05 01:10 by crell