rfc:cachediterable

Differences

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

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
rfc:cachediterable [2021/06/13 16:11]
tandre indentation
rfc:cachediterable [2021/06/29 14:24]
tandre
Line 1: Line 1:
-====== PHP RFC: ImmutableKeyValueSequence (immutable, rewindable, memory-efficient, allows any key&repeating keys)  ====== +====== PHP RFC: ImmutableIterable (immutable, rewindable, memory-efficient, allows any key&repeating keys)  ====== 
-  * Version: 0.3+  * Version: 0.4
   * Date: 2021-02-06   * Date: 2021-02-06
   * Author: Tyson Andre, tandre@php.net   * Author: Tyson Andre, tandre@php.net
-  * Status: Under Discussion+  * Status: Declined
   * Implementation: https://github.com/php/php-src/pull/6655   * Implementation: https://github.com/php/php-src/pull/6655
   * First Published at: https://wiki.php.net/rfc/cachediterable   * First Published at: https://wiki.php.net/rfc/cachediterable
Line 11: Line 11:
 Currently, PHP does not provide a built-in way to store the state of an arbitrary iterable for reuse later (when the iterable has arbitrary keys, or when keys might be repeated). It would be useful to do so for many use cases, such as: Currently, PHP does not provide a built-in way to store the state of an arbitrary iterable for reuse later (when the iterable has arbitrary keys, or when keys might be repeated). It would be useful to do so for many use cases, such as:
  
-  - Creating a rewindable copy of a non-rewindable Traversable (e.g. a ''Generator'') before passing that copy to a function that consumes an iterable/Traversable. (''new CachedIterator(my_generator())'')+  - Creating a rewindable copy of a non-rewindable Traversable (e.g. a ''Generator'') before passing that copy to a function that consumes an iterable/Traversable. (''new ImmutableIterable(my_generator())'')
   - Generating an ''IteratorAggregate'' from a class still implementing ''Iterator'' (e.g. ''SplObjectStorage'') so that code can independently iterate over the key-value sequences. \\ (e.g. ''foreach ($immutableKeyValueSequence as $k1 => $v1) { foreach ($immutableKeyValueSequence as $k2 => $v2) { /* process pairs */ } }'')   - Generating an ''IteratorAggregate'' from a class still implementing ''Iterator'' (e.g. ''SplObjectStorage'') so that code can independently iterate over the key-value sequences. \\ (e.g. ''foreach ($immutableKeyValueSequence as $k1 => $v1) { foreach ($immutableKeyValueSequence as $k2 => $v2) { /* process pairs */ } }'')
   - Providing internal or userland helpers such as ''iterable_flip(iterable $input)'', ''iterable_take(iterable $input, int $limit)'', ''iterable_chunk(iterable $input, int $chunk_size)'' that act on iterables with arbitrary key/value sequences and have return values including iterables with arbitrary key/value sequences   - Providing internal or userland helpers such as ''iterable_flip(iterable $input)'', ''iterable_take(iterable $input, int $limit)'', ''iterable_chunk(iterable $input, int $chunk_size)'' that act on iterables with arbitrary key/value sequences and have return values including iterables with arbitrary key/value sequences
Line 20: Line 20:
 ===== Proposal ===== ===== Proposal =====
  
-Add a class ''ImmutableKeyValueSequence'' that contains an immutable copy of the keys and values of the iterable it was constructed from. (references inside of arrays within those keys/values will remain modifiable references, and objects within those keys/values will remain mutable)+Add a class ''ImmutableIterable'' that contains an immutable copy of the keys and values of the iterable it was constructed from. (references inside of arrays within those keys/values will remain modifiable references, and objects within those keys/values will remain mutable)
  
 <code php> <code php>
-final class ImmutableKeyValueSequence implements IteratorAggregate, Countable, JsonSerializable+final class ImmutableIterable implements  
 +    IteratorAggregate, 
 +    Countable, 
 +    JsonSerializable 
 { {
     public function __construct(iterable $iterator) {}     public function __construct(iterable $iterator) {}
Line 29: Line 32:
     public function count(): int {}     public function count(): int {}
     // [[$key1, $value1], [$key2, $value2]]     // [[$key1, $value1], [$key2, $value2]]
-    public static function fromPairs(array $pairs): ImmutableKeyValueSequence {}+    public static function fromPairs(array $pairs): ImmutableIterable {}
     // [[$key1, $value1], [$key2, $value2]]     // [[$key1, $value1], [$key2, $value2]]
     public function toPairs(): array{}     public function toPairs(): array{}
     public function __serialize(): array {}  // [$k1, $v1, $k2, $v2,...]     public function __serialize(): array {}  // [$k1, $v1, $k2, $v2,...]
     public function __unserialize(array $data): void {}     public function __unserialize(array $data): void {}
 +    public static function __set_state(array $array): ImmutableIterable {}
  
     // useful for converting iterables back to arrays for further processing     // useful for converting iterables back to arrays for further processing
Line 48: Line 52:
 </code> </code>
  
-ImmutableKeyValueSequences are IteratorAggregates, so foreach loops do not interfere with each other.+ImmutableIterables are IteratorAggregates, so foreach loops do not interfere with each other.
  
 <code php> <code php>
-$x = new ImmutableKeyValueSequence([0 => 100, 'key' => 'value']);+$x = new ImmutableIterable([0 => 100, 'key' => 'value']);
 foreach ($x as $key1 => $value1) { foreach ($x as $key1 => $value1) {
     echo "$key1 $value1:\n";     echo "$key1 $value1:\n";
Line 68: Line 72:
 </code> </code>
  
-==== ImmutableKeyValueSequences can be used to cache and efficiently process results of Traversables ====+==== ImmutableIterables can be used to cache and efficiently process results of Traversables ====
  
-ImmutableKeyValueSequences can be created from any iterable (arrays or Traversables). They eagerly evaluate the results of Traversables (e.g. Generators) and store an exact copy of the keys and values that can be processed in many ways.+ImmutableIterables can be created from any iterable (arrays or Traversables). They eagerly evaluate the results of Traversables (e.g. Generators) and store an exact copy of the keys and values that can be processed in many ways.
  
 In comparison to php's ''array''/''ArrayObject'' type: In comparison to php's ''array''/''ArrayObject'' type:
-  +
   * Arrays can only store integers and strings   * Arrays can only store integers and strings
   * Arrays coerce stringified integers to integers, potentially causing unexpected Errors/notices (especially when ''strict_types=1'')   * Arrays coerce stringified integers to integers, potentially causing unexpected Errors/notices (especially when ''strict_types=1'')
Line 87: Line 91:
 } }
  
-$x = new ImmutableKeyValueSequence(my_generator());+$x = new ImmutableIterable(my_generator());
 foreach ($x as $k => $v) { foreach ($x as $k => $v) {
     printf("%s: %s\n", json_encode($k), json_encode($v));     printf("%s: %s\n", json_encode($k), json_encode($v));
Line 109: Line 113:
 </code> </code>
  
-==== ImmutableKeyValueSequences are immutable ====+==== ImmutableIterables are immutable ====
  
-ImmutableKeyValueSequence is a final class.+ImmutableIterable is a final class.
  
-Dynamic properties are forbidden on ImmutableKeyValueSequences.+Dynamic properties are forbidden on ImmutableIterables.
  
-The keys and values of the ImmutableKeyValueSequence cannot be modified or appended to after an instance is constructed, though objects and references within those values can be modified.+The keys and values of the ImmutableIterable cannot be modified or appended to after an instance is constructed, though objects and references within those values can be modified.
  
 This makes it useful for returning to wrap the keys and values that would be returned by a generator or single-use ''Iterator'' (it can't be modified after being constructed by other applications or libraries) This makes it useful for returning to wrap the keys and values that would be returned by a generator or single-use ''Iterator'' (it can't be modified after being constructed by other applications or libraries)
  
-==== ImmutableKeyValueSequences can be created from pairs ====+==== ImmutableIterables can be created from pairs ====
  
 This can be done imperatively, to avoid the need to manually create a generator with the sequence of keys and values to pass to the constructor. The values of an iterable (array or Traversable) can be used. This can be done imperatively, to avoid the need to manually create a generator with the sequence of keys and values to pass to the constructor. The values of an iterable (array or Traversable) can be used.
  
 <code php> <code php>
-$it = ImmutableKeyValueSequence::fromPairs([['first', 'x'], [(object)['key' => 'value'], null]]);+$it = ImmutableIterable::fromPairs([['first', 'x'], [(object)['key' => 'value'], null]]);
 foreach ($it as $key => $value) { foreach ($it as $key => $value) {
     printf("key=%s value=%s\n", json_encode($key), json_encode($value));     printf("key=%s value=%s\n", json_encode($key), json_encode($value));
Line 134: Line 138:
 var_dump($it); var_dump($it);
 /* /*
-object(ImmutableKeyValueSequence)#2 (1) {+object(ImmutableIterable)#2 (1) {
   [0]=>   [0]=>
   array(2) {   array(2) {
Line 148: Line 152:
 </code> </code>
  
-ImmutableKeyValueSequences can also be converted back into pairs for further processing (e.g. using the wide array of helper methods php has for processing arrays):+ImmutableIterables can also be converted back into pairs for further processing (e.g. using the wide array of helper methods php has for processing arrays):
  
 <code php> <code php>
-php > $reversedIt = ImmutableKeyValueSequence::fromPairs(array_reverse($it->toPairs()));+php > $reversedIt = ImmutableIterable::fromPairs(array_reverse($it->toPairs()));
 php > echo json_encode($reversedIt->toPairs()); php > echo json_encode($reversedIt->toPairs());
 [[{"key":"value"},null],["first","x"]] [[{"key":"value"},null],["first","x"]]
Line 158: Line 162:
 ===== Benchmarks ===== ===== Benchmarks =====
  
-==== ImmutableKeyValueSequences are memory-efficient ====+==== ImmutableIterables are memory-efficient ====
  
-Similarly to how ''SplFixedArray'' is a memory-efficient way to store a list of values, ''ImmutableKeyValueSequence'' is a memory-efficient way to store a sequence of arbitrary keys and values.+Similarly to how ''SplFixedArray'' is a memory-efficient way to store a list of values, ''ImmutableIterable'' is a memory-efficient way to eagerly evaluate and store a sequence of arbitrary keys and values.
  
 <code php> <code php>
Line 175: Line 179:
     gc_collect_cycles();     gc_collect_cycles();
     $before = memory_get_usage();     $before = memory_get_usage();
-    // create a ImmutableKeyValueSequence from an **associative** array of size $n +    // create a ImmutableIterable from an **associative** array of size $n 
-    $result = new ImmutableKeyValueSequence(array_flip(range(10, 10 + $n - 1)));+    $result = new ImmutableIterable(array_flip(range(10, 10 + $n - 1)));
     $after = memory_get_usage();     $after = memory_get_usage();
-    printf("ImmutableKeyValueSequence memory: (n=%5d) %7d bytes\n", count($result), $after - $before);+    printf("ImmutableIterable memory: (n=%5d) %7d bytes\n", count($result), $after - $before);
 } }
 foreach ([1, 8, 12, 16, 2**16] as $n) { foreach ([1, 8, 12, 16, 2**16] as $n) {
Line 185: Line 189:
 } }
 /* /*
-array memory:                     (n=    1)     376 bytes +array memory:             (n=    1)     376 bytes 
-ImmutableKeyValueSequence memory: (n=    1)      88 bytes +ImmutableIterable memory: (n=    1)      88 bytes 
-array memory:                     (n=    8)     376 bytes +array memory:             (n=    8)     376 bytes 
-ImmutableKeyValueSequence memory: (n=    8)     312 bytes +ImmutableIterable memory: (n=    8)     312 bytes 
-array memory:                     (n=   12)    1336 bytes +array memory:             (n=   12)    1336 bytes 
-ImmutableKeyValueSequence memory: (n=   12)     440 bytes +ImmutableIterable memory: (n=   12)     440 bytes 
-array memory:                     (n=   16)    1336 bytes +array memory:             (n=   16)    1336 bytes 
-ImmutableKeyValueSequence memory: (n=   16)     568 bytes +ImmutableIterable memory: (n=   16)     568 bytes 
-array memory:                     (n=65536) 4198480 bytes +array memory:             (n=65536) 4198480 bytes 
-ImmutableKeyValueSequence memory: (n=65536) 2097232 bytes+ImmutableIterable memory: (n=65536) 2097232 bytes
  */  */
 </code> </code>
  
-==== ImmutableKeyValueSequences are much more efficient than a polyfill object ====+==== ImmutableIterables are much more efficient than a polyfill object ====
  
 For a simple example, this uses much less time to construct. It is almost 6 times faster to iterate over and process results than a polyfill in that example, and uses half as much additional memory. For a simple example, this uses much less time to construct. It is almost 6 times faster to iterate over and process results than a polyfill in that example, and uses half as much additional memory.
Line 205: Line 209:
 <?php <?php
 /* /*
-Time to construct  PolyfillKeyValueSequence: 0.087226, Time to iterate: 0.183351, memory usage: 67117328+Time to construct PolyfillImmutableIterator: 0.244787 
 +Time to iterate: 0.183351, memory usage: 67117328
 result:999000000 result:999000000
-Time to construct ImmutableKeyValueSequence: 0.051358, Time to iterate: 0.021905, memory usage: 32002128+ 
 +Time to construct         ImmutableIterable: 0.130534 
 +Time to iterate: 0.021905, memory usage: 32002128
 result:999000000 result:999000000
  */  */
Line 215: Line 222:
  *  *
  * Barely any of the functionality in the proposal is implemented.  * Barely any of the functionality in the proposal is implemented.
- * This is just here to compare a fast (in terms of time to iterate) userland polyfill against ImmutableKeyValueSequence.+ * This is just here to compare a fast (in terms of time to iterate) userland polyfill 
 + against ImmutableIterable.
  *  *
  * Not an IteratorAggregate for simplicity.  * Not an IteratorAggregate for simplicity.
  */  */
-class PolyfillKeyValueSequence implements Iterator {+class PolyfillImmutableIterator implements Iterator {
     public $i = 0;     public $i = 0;
     public $count = 0;     public $count = 0;
Line 263: Line 271:
     gc_collect_cycles();     gc_collect_cycles();
     $memory_usage_2 = memory_get_usage();     $memory_usage_2 = memory_get_usage();
-    printf("Time to construct %16s: %.6f, Time to iterate: %.6f, memory usage: %d\nresult:%d\n",+    printf("Time to construct %25s: %.6f\nTime to iterate: %.6f, memory usage: %d\nresult:%d\n\n",
         $class, $t2 - $t1, $t3 - $t2, $memory_usage_2 - $memory_usage_1, $total);         $class, $t2 - $t1, $t3 - $t2, $memory_usage_2 - $memory_usage_1, $total);
 } }
-benchmark(PolyfillKeyValueSequence::class); +benchmark(PolyfillImmutableIterator::class); 
-benchmark(ImmutableKeyValueSequence::class);+benchmark(ImmutableIterable::class);
 </code> </code>
-==== ImmutableKeyValueSequences support constant-time access to keys and values ====+==== ImmutableIterables support constant-time access to keys and values ====
  
-ImmutableKeyValueSequences support constant-time access to keys and values, allowing the result to be used in a wide variety of ways in an applicat.+''ImmutableIterable''support constant-time access to keys and values, allowing the result to be used in a wide variety of ways in an application.
 For example, it is possible to do binary search on keys (and/or values) without using any additional time or memory to create a copy of the keys. For example, it is possible to do binary search on keys (and/or values) without using any additional time or memory to create a copy of the keys.
 (Same for values). (Same for values).
Line 281: Line 289:
  * Returns count($it) if all keys are smaller than $target.  * Returns count($it) if all keys are smaller than $target.
  */  */
-function do_binary_search_on_key(ImmutableKeyValueSequence $it, int $target) {+function do_binary_search_on_key(ImmutableIterable $it, int $target) {
     $lowOffset = 0;     $lowOffset = 0;
     $highOffset = count($it) - 1;     $highOffset = count($it) - 1;
Line 295: Line 303:
         }         }
     }     }
-    echo "offset $lowOffset has the first key ({$it->keyAt($lowOffset)}) >= $target : associated value={$it->valueAt($lowOffset)}\n";+    echo "offset $lowOffset has the first key ({$it->keyAt($lowOffset)}) >= $target " . 
 +         ": associated value={$it->valueAt($lowOffset)}\n";
     return $lowOffset;     return $lowOffset;
 } }
Line 306: Line 315:
 } }
 ksort($data); ksort($data);
-$it = new ImmutableKeyValueSequence($data);+$it = new ImmutableIterable($data);
  
 do_binary_search_on_key($it, mt_rand()); do_binary_search_on_key($it, mt_rand());
Line 325: Line 334:
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
-None, except that the class name ''ImmutableKeyValueSequence'' will be declared by PHP and conflict with applications declaring the same class name in that namespace.+None, except that the class name ''ImmutableIterable'' will be declared by PHP and conflict with applications declaring the same class name in that namespace.
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
Line 332: Line 341:
 ===== Future Scope ===== ===== Future Scope =====
  
-  * This will enable adding internal iterable functions such as ''*take(iterable $input, int $limit): ImmutableKeyValueSequence'' or ''*flip(iterable $input): ImmutableKeyValueSequence'' or +  * This will enable adding internal iterable functions such as ''*take(iterable $input, int $limit): ImmutableIterable'' or ''*flip(iterable $input): ImmutableIterable'' or 
-  * More methods may be useful to add to ''ImmutableKeyValueSequence'', e.g. for returning a sorted copy, returning a slice(range of entries), returning a copy sorted by keys/values, quickly returning the index/corresponding value of the first occurrence of ''mixed $key'' etc.+  * More methods may be useful to add to ''ImmutableIterable'', e.g. for returning a sorted copy, returning a slice(range of entries), returning a copy sorted by keys/values, quickly returning the index/corresponding value of the first occurrence of ''mixed $key'' etc.
   * This may or may not be useful for future data types, e.g. a ''MapObject'' (hash map on any key type) type and may potentially be useful for converting some existing internal/user-defined ''Iterable'' types to ''IteratorAggregate'' types.   * This may or may not be useful for future data types, e.g. a ''MapObject'' (hash map on any key type) type and may potentially be useful for converting some existing internal/user-defined ''Iterable'' types to ''IteratorAggregate'' types.
-    * A new ''IterableAggregate'' subclass such as ''CachedIterator'' could be added to compute values [[https://en.wikipedia.org/wiki/Lazy_evaluation|lazily(on-demand)]] in contrast to ''ImmutableKeyValueSequence'', which [[https://en.wikipedia.org/wiki/Eager_evaluation|evaluates the entire iterable in the constructor]].+    * A new ''IterableAggregate'' subclass such as ''CachedIterator''/''CachedIterable'' could be added to compute values [[https://en.wikipedia.org/wiki/Lazy_evaluation|lazily(on-demand)]] in contrast to ''ImmutableIterable'', which [[https://en.wikipedia.org/wiki/Eager_evaluation|evaluates the entire iterable in the constructor]].
  
-===== Proposed Voting Choices ===== +===== Vote ===== 
-Yes/No, requiring a 2/3 majority.+This is a Yes/No vote, requiring a 2/3 majority. Voting started on June 15, 2021 and ends on June 29, 2021. 
 + 
 +<doodle title="Add ImmutableIterable to core" auth="tandre" voteType="single" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle> 
 + 
 +==== Poll: Reason for voting against this RFC ==== 
 + 
 +<doodle title="Reasons for voting against the ImmutableIterable RFC" auth="tandre" voteType="multi" closed="true"> 
 +   * Object to the namespace choice 
 +   * Object to the name 
 +   * Object to the implementation 
 +   * Don't see a use case 
 +   * Other 
 +</doodle>
  
 ===== References ===== ===== References =====
  
   * [[https://externals.io/message/113061#113066|Proposal: Add ReverseArrayIterator and ForwardArrayIterator to SPL]]   * [[https://externals.io/message/113061#113066|Proposal: Add ReverseArrayIterator and ForwardArrayIterator to SPL]]
-  * [[rfc:cachediterable_straw_poll|Straw poll: Namespace to use for ImmutableKeyValueSequence and iterable functionality]]+  * [[rfc:cachediterable_straw_poll|Straw poll: Namespace to use for CachedIterable and iterable functionality]] 
 +  * https://externals.io/message/114834  RFC: CachedIterable (rewindable, allows any key&repeating keys) 
 +  * [[https://externals.io/message/114887|[VOTE] ImmutableIterable (immutable, rewindable, allows any key&repeating keys)]]
  
 ===== Rejected Features ===== ===== Rejected Features =====
 +
 +==== Rejected: Alternative namespaces ====
 +
 +[[rfc:cachediterable_straw_poll|Straw poll: Namespace to use for CachedIterable and iterable functionality]] did not indicate a majority of voters preferred alternative namespace choices for a namespace over not using a namespace for newly added caches. This chooses the global namespace to maintain consistency with existing spl classes and interfaces.
 +
  
 ==== Rejected: ArrayAccess ==== ==== Rejected: ArrayAccess ====
Line 356: Line 387:
 ==== Rejected: Lazy Evaluation ==== ==== Rejected: Lazy Evaluation ====
  
-''ImmutableKeyValueSequence'' evaluates the entire iterable in its constructor ([[https://en.wikipedia.org/wiki/Eager_evaluation|eagerly]] instead of [[https://en.wikipedia.org/wiki/Lazy_evaluation|lazily]]) for the following reasons:+''ImmutableIterable'' evaluates the entire iterable in its constructor ([[https://en.wikipedia.org/wiki/Eager_evaluation|eagerly]] instead of [[https://en.wikipedia.org/wiki/Lazy_evaluation|lazily]]) for the following reasons:
  
-  * Exceptions will be thrown during construction instead of during iteration or call to count()/keyAt()/valueAt() - it would be unintuitive for those to throw ''SomeUserlandFrameworkException''+  * If this is generated from a data structure, the behavior may be unintuitive if the underlying data is modified while iterating over the sequence of keys and values if this were to be evaluated lazily instead of eagerly. 
 +  * Evaluating the entire iterable in the constructor ensures that exceptions will be thrown during construction instead of during iteration or call to count()/keyAt()/valueAt() - it would be unintuitive for those iteration methods to throw ''SomeUserlandFrameworkException''
   * This is easier to understand, debug, serialize, and represent   * This is easier to understand, debug, serialize, and represent
   * If the underlying iterable (e.g. a Generator) has side effects, having those side effects take place immediately instead of being interleaved with other parts of the program may be easier to reason about.   * If the underlying iterable (e.g. a Generator) has side effects, having those side effects take place immediately instead of being interleaved with other parts of the program may be easier to reason about.
   * The majority of use cases of ''Traversable''s would iterate over the entire Traversable at some point.   * The majority of use cases of ''Traversable''s would iterate over the entire Traversable at some point.
   * Eagerly evaluating iterables reduces the memory needed by the implementation. The amount of memory needed to represent this is much lower (without the need to store the underlying iterable, potentially the most recent exception(s) thrown by the undlying iterable, etc).   * Eagerly evaluating iterables reduces the memory needed by the implementation. The amount of memory needed to represent this is much lower (without the need to store the underlying iterable, potentially the most recent exception(s) thrown by the undlying iterable, etc).
 +
  
 The addition of an iterable library class that evaluates arguments on-demand is mentioned in the "future scope" section. The addition of an iterable library class that evaluates arguments on-demand is mentioned in the "future scope" section.
Line 382: Line 415:
 to make code harder to understand. to make code harder to understand.
  
-4) It is possible to implement a lazy alternative to (ImmutableKeyValueSequence) that only loads values as needed.+4) It is possible to implement a lazy alternative to (ImmutableIterable) that only loads values as needed.
 However, I hadn't proposed it due to doubts that 2/3 of voters would consider it widely useful However, I hadn't proposed it due to doubts that 2/3 of voters would consider it widely useful
 enough to be included in php rather than as a userland or PECL library. enough to be included in php rather than as a userland or PECL library.
Line 430: Line 463:
  
   * 0.2: Use optimized build with opcache enabled for benchmark timings   * 0.2: Use optimized build with opcache enabled for benchmark timings
-  * 0.3: Rename from CachedIterable to ImmutableKeyValueSequence (the lack of clarity about the functionality associated with the name CachedIterable being eagerly evalulated was mentioned *after* most of the responses to the straw poll were already submitted)+  * 0.3: Rename from ''CachedIterable'' to ''ImmutableKeyValueSequence'' (the lack of clarity about the functionality associated with the name ''CachedIterable'' being eagerly evalulated was mentioned *after* most of the responses to the straw poll were already submitted). \\ Other names starting with ''Cached*'' were rejected for the same reason. 
 +  * 0.3.1: Add ''__set_state'' 
 +  * 0.4.0: Rename from ''ImmutableKeyValueSequence'' to ''ImmutableIterable''
  
rfc/cachediterable.txt · Last modified: 2021/06/29 14:24 by tandre