Iterables (and possible breaking change for `$each` and other navigators)
See original GitHub issueI’d like to add support for iterables. This would automatically add support for Map
, Immutable.js, etc.
One immediately obvious problem is the semantics of $each
. Currently, $each
iterates over each value of an array or object. This mirrored Lodash’s _.each
semantics. If you want to iterate over each pair, you use $eachPair
.
Iterating over an iterable though is meant to iterate over each of its “entries”. So in the case of Map
, that’s each key/value pair. In the case of an array, that’s each value. There’s also a stage 4 proposal for Object.entries
which behaves the same way as Map.prototype.entries
.
This probably means that $each
should probably change to be values for an array and pairs for an object. But that means this:
select([$each], {x: 1, y: 2})
// [1, 2]
Would have to become this:
select([$each, 1], {x: 1, y: 2})
// [1, 2]
Which is kind of ugly. And it prevents you from being able to use the same query on an array or an object, which is often useful. To mitigate that, the current $each
behavior could probably move to $eachValue
(which is what it was originally named). If an object has a .values
function, that can be used as the iterable for $eachValue
. So you could have:
select([$eachValue], {x: 1, y: 2})
// [1, 2]
Another option would be to leave $each
as-is and add an $eachEntry
navigator, but that feels like it’s just adding more API surface area.
A less significant question is whether or not to keep $eachPair
after this change. It has arguably limited usefulness for arrays, and for objects, it would effectively be a duplicate of $each
. There’s no pairs
method for iterables, but looking for keys
function should be enough of a clue to determine if entries
returns pairs. There’s no rule that says an iterable has to implement these though, or that they have to keep the naming convention, so it’s kind of ambiguous how it should behave in those cases. Throw an error? qim
could offer a custom protocol for filling in these gaps (like transducers).
There’s also the question of $first
and $last
. Currently, they return the first and last value. So after this change, should they return the first and last entry instead? Should there be an equivalent $firstValue
and $lastValue
to grab the first and last value? The first and last value of an object has less meaning than for arrays, so I think the semantics change here is of less impact, and $firstValue
and $lastValue
can wait until there’s a real need.
I’ll have to dig into code more to figure out how iterables will affect creating custom navigators, i.e. $traverse
. I wanted to surface this change right away though.
Issue Analytics
- State:
- Created 6 years ago
- Comments:9 (6 by maintainers)
Top GitHub Comments
That’s kind of where I’m landing @bryanhelmig.
There already is an
$eachPair
navigator, and I think its behavior is what you’re describing for$eachEntry
. When I mentioned$eachEntry
, I was talking about perhaps adding something that iterated according toSymbol.iterator
behavior:However, I was surprised (just now) to find out that
Array.prototype.entries
actually returns pairs.So I guess “entry” is just a synonym for “pairs”, so scratch a lot of what I said when I opened this issue, since I should have done a bit more digging first! I think the current behavior of
$each
,$eachPair
and$eachKey
is pretty reasonable, so I’m going to leave that as-is. Point taken that$eachKey
is unnecessary sugar, but it’s a tad more performant than$eachPair, 0
, so I’m going to leave it for now too.I still think there’s some ambiguity around
$first
,$slice
, etc. that should be cleaned up, and that might be better handled by adding$values
,$keys
, and$pairs
, so I’ll leave this open while I play around with a lazy iterator there.Closing this issue, but I wrote another book if you want to check that out.