- Proposal: HXP-0005
- Author: Dan Korostelev
- Status: implemented in 4.0.0
Support easy iteration over key-value pairs in the for
loop syntax.
(see also: HaxeFoundation/haxe#2421)
Very often when iterating over various collections we want to have both element key/index and its value as local variables. Right now, there's no special syntax for that in Haxe, so we have to iterate over keys/indices and extract the value from the collection at each step. This makes code less concise and declarative and thus more error-prone:
for (key in map.keys()) {
var value = map.get(key);
trace(key, value);
}
for (index in 0...array.length) {
var value = array[index];
trace(index, value);
}
What we could have instead is this:
for (key => value in map) {
trace(key, value);
}
for (index => value in array) {
trace(index, value);
}
As shown in the "Motivation" section above, the proposed syntax would be for (key => value in collection)
.
This syntax seems logical and consistent with the current map declaration syntax ([key => value]
).
It's also not a breaking change, because at the moment the only allowed AST node before the in
is a simple identifier.
For this to work, we introduce a new standard iterable type:
typedef KeyValueIterator<K,V> = Iterator<{key:K, value:V}>;
typedef KeyValueIterable<K,V> = {
function keyValueIterator():KeyValueIterator<K,V>;
}
When typing the for (key => value in collection) {}
expression, we handle it in a similar way as normal iterators, that is:
-
if
collection
conforms toKeyValueIterator<K,V>
, generate:while (collection.hasNext()) { var tmp = collection.next(); var key = tmp.key; var value = tmp.value; }
-
if
collection
conforms toKeyValueIterable<K,V>
, generate:var iterator = collection.keyValueIterator(); while (iterator.hasNext()) { var tmp = iterator.next(); var key = tmp.key; var value = tmp.value; }
-
otherwise, emit the
Type has no field keyValueIterator
error
For the most common case (for
loop), if implemented properly, key-value iterators will be fully
inlined and temp variables will be optimized away, so it should be at least as good as hand-written
code similar to the one in the "Motivation" section. For example, see this try.haxe snippet.
For some specific collection implementations, I imagine the key/value iterator could be faster than
iterating over keys and getting the value each time, if the collection can provide pairs directly.
Actually, even standard Array
might benefit from this, since it could save some bounds checking on getting values.
One performance-related concern is the use of structural typing, which can be an issue on static
targets in their current implementation, however this is a more general problem which also applies to
normal iterators, so it's out of scope for this proposal. Still, we might want to provide a standard
optimized implementation of the readonly KeyValuePair
type implementation that would be used for key/value iterators
instead of {key:K, value:V}
.
This shouldn't break much: the key => value
before in
within for
is currently forbidden,
and it doesn't seem like there's any functions named keyValueIterator
in public Haxe code.
No real alternative. Of course, one could macro-process every for
loop with a global @:build
macro,
but that would be an overkill for such simple feature.