Map: filter/map and more
Was it an intended decision not to have
Map#map
,Map#filter
?
Only with regard to time. I expect there will be substantial additions to Map and Set in ES7 (as long as the work is done, of course).
Will it make sense having a nice declarative syntax like:
new Map({ [Names.ID]: id, [Names.EMAIL]: email, needsReload: true, })
This doesn't work because Maps allow objects as keys.
I expect there will be substantial additions to Map and Set in ES7 (as long as the work is done, of course).
Hm, sounds like two copy-pasting algorithms from the same Array#map
,
Array#filter
, or just tweaking the Map#forEach
(I might be missing
something).
This doesn't work because Maps allow objects as keys.
Yes, I said it myself above. That's the thing -- I'd like to thing about some special syntax maybe. Don't know yet, probably map as a construct for declarative cases like:
// Declarative
Map {
[foo]: 1,
bar: 2,
}
// Imperative (via constructor)
new Map([ // brrr..
[foo, 1],
['bar', 2]
])
Then thing is: if we'd like to use maps for such use-case, it brings us back to that inconvenient imperative style of assignments (even worse, since you have to repeat that
.set(...)
constantly):var requestData = new Map(); requestData.set(Names.ID, id); requestData.set(Names.EMAIL, email); requestData.set('needsReload', id); ...
Note that you can chain:
var requestData = new Map()
.set(Names.ID, id)
.set(Names.EMAIL, email)
.set('needsReload', id);
Not too bad, IMO.
Not sure if this is sufficient motivation to accelerate the timeline for adding suitable parallels from Array.prototype, but it may be worth recalling what developers did last time there were obvious "gaps" in what a native prototype provided. Array#contains, anyone?
On Wed, Oct 1, 2014 at 3:51 PM, Jeremy Martin <jmar777 at gmail.com> wrote:
Not sure if this is sufficient motivation to accelerate the timeline
It's not about "motivation", it's about realistic time constraints. TC39 has already had to push out 6 months and that was not taken well by both the community and by Ecma. Further delay is not an option.
for adding suitable parallels from Array.prototype, but it may be worth recalling what developers did last time there were obvious "gaps" in what a native prototype provided. Array#contains, anyone?
As a community we'll simply have to reject the use of code that directly modifies built-ins. This is also not a very strong argument in the ES6 world where built-ins can be subclassed.
FWIW: I’ll probably use the following work-arounds. I don’t expect performance to be an issue for my applications; it’d be interesting to hear if it becomes a problem for someone.
let map2 = new Map(map1.entries().filter((key, value) => key >= 0));
let map2 = new Map(map1.entries().map((key, value) => [key * 2, value * 2]));
Almost. Correct versions:
let map2 = new Map(map1.entries().filter(([key, value]) => key >= 0));
let map2 = new Map(map1.entries().map(([key, value]) => [key * 2, value * 2]));
Also, if there are written proposals, the userland extensions (if they really must be) can follow those proposals.
It's not about "motivation", it's about realistic time constraints.
Just out of curiosity: what's the realistic "out of time" issue here? Actually, I think having a ready and working algorithm draft on github gist will help discussing this faster and in realistic time frames (I've should've done before starting this thread; otherwise, these are "too abstract time-stoppers" for me -- unless you know specific big issues that will be hard to implement).
As a community we'll simply have to reject the use of code that directly modifies built-ins.
Actually the case with "borken" Array#contains
which is so actively
discussed, is a good example. I think very likely Map#map
, and
Map#filter
will be added manually if not in ES6.
let map2 = new Map(map1.entries().filter((key, value) => key >= 0));
let map2 = new Map(map1.entries().map((key, value) => [key * 2, value *
2]));
entries()
returns an iterator, not an array.
Just out of curiosity: what's the realistic "out of time" issue here?
If this was really pressing, why wasn't it on any meeting agendas in the last year?
The spec must be essentially finished by the next meeting (the last meeting in 2014). Finalizing the Loader details, super class instantiation (you might recall that was an issue recently), RegExp work in Annex B (may require lexical grammar changes), revising, reviewing, etc. (I know there is a lot more here)... All of this takes precedence over API surface additions, whether complete or not. Additionally, the committee is trying establish a new process for all new features. Domenic could've tried to push Array.prototype.contains into ES6, but he's following the process. tc39/ecma262
I think very likely
Map#map
, andMap#filter
will be added manually if not in ES6.
See my response, I doubt that a one year wait until ES7 will really ruin the chances for map and filter methods. In the meantime, write the spec, get it added to tc39/ecma262 and enforce the semantics on all userland implementations. Time will fly, I promise.
Ah, thanks! Then I’d wrap the result of entries()
in Array.from()
. In ES7, we’ll hopefully be able to use comprehensions or iterator helper functions.
entries() returns an iterator, not an array.
let map2 = new Map([...map1].map(([k, v]) => [k, v * 2])
Sweet!
Ah, thanks! Then I’d wrap the result of
entries()
inArray.from()
.
Don't need to do that either.
let map1 = new Map([[-1, -1], [0, 0], [1, 1], [2, 2]]);
let map2 = new Map([...map1].filter(([key, value]) => key >= 0));
let map3 = new Map([...map1].map(([key, value]) => [key * 2, value * 2]));
console.log([...map2]); // [[0, 0], [1, 1], [2, 2]]
console.log([...map3]); // [[-2, -2], [0, 0], [2, 2], [4, 4]]
let map2 = new Map([...map1].map(([k, v]) => [k, v * 2])
Sorry for the almost-dup, didn't see this while I was typing.
OK, FWIW:
- map: gist.github.com/DmitrySoshnikov/a218700746b2d7a7d2c8
- filter: gist.github.com/DmitrySoshnikov/82b46f4674acee5cedc2
Does it look like a vital algorithm? And if no one sees big/any issue with, can it potentially be considered for inclusion? I'm not gonna push it of course taking into account higher-pri things, however, if it seems to everyone trivial -- why not.
(Otherwise, at least it can be a source for monkey-patching the
Map.prototype
if it's only for ES7).
Note that you can chain:
var requestData = new Map() .set(Names.ID, id) .set(Names.EMAIL, email) .set('needsReload', id);
Not too bad, IMO.
Not ideal either. Usually langs provide nice declarative syntax for such things. E.g. we have the same in the HACK language, and use it well everyday when need a map.
But this part is of course not for ES6, hope ES7-ish.
If this was really pressing, why wasn't it on any meeting agendas in the last year?
I'm sorry, as mentioned at the beginning of this thread, I unfortunately
missed this part of the spec when the decisions were made. And since we
just recently started to polyfill the Map
in our codebase following the
spec, I was surprised that neither map
, nor filter
are actually in the
spec. I thought there were some real big issues, and wanted to clarify
them. If it's only "out of time", I'd of course propose it for the agenda.
... All of this takes precedence over API surface additions, whether complete or not.
Yeah, absolutely agree that these are higher-pri.
Time will fly, I promise.
Sure, at first glance the algorithms are trivial enough as posted above.
Usually langs provide nice declarative syntax for such things.
We could definitely have Map and Set literals:
const map = {1 => "one", "two" => true, false => "three"};
const set = {<1, "two", false>};
If you still buy Harmony of My Dreams, prefix #
before {
to get
immutable value-type forms.
I don't mind reusing =>
in initialiser context where :
would go, but
perhaps someone sees a problem I don't. The Set literal hack of {<
and
>}
seems necessary given object initialiser property assignment
shorthand syntax ({x, y}
for {x:x, y:y}
). Some kind of hack is required,
yet losing {
and }
as outermost bracketing characters for Set seems
worse than any digraph or token-pair alternative.
const map = {1 => "one", "two" => true, false => "three"};
const set = {<1, "two", false>};
Yeah, something like this (I actually like these two forms); later for ES7.
Prefix
#
before{
to get immutable value-type forms.
Yep, why not, can be (re)considered as well I guess.
const map = {1 => "one", "two" => true, false => "three"};
Would that be tricky to parse if keys can be arbitrary expressions (incl. array literals)?
I don't mind reusing
=>
in initialiser context where:
would go, but perhaps someone sees a problem I don't.
Using something other than a colon seems a good idea, to make it clear that any kind of value can be used as keys.
Another possibility:
const map = {: 1 => "one", "two" => true, false => "three" :};
const set = {. 1, "two", false .};
Would that be tricky to parse?
The issue would be arrow function expression keys:
const map = {x => x*x => "square"};
But such a key could not be found without capturing the reference:
let f;
const map = {f = x => x*x => "square"};
let g = map.get(f);
assertEq(g, f);
It's all parseable but an eyesore; but also unlikely/contrived.
Another possibility:
const map = {: 1 => "one", "two" => true, false => "three" :}; const set = {. 1, "two", false .};
Not sure what's best, but .
is visually light, also used in the language
in ways independent from Sets.
I don't see the need for extra delimiters for map literals -- why tax two chars each literal just for parallelism w.r.t. set literals?
OK, FWIW:
Please make a PR to tc39/ecma262
Thanks, done. tc39/ecma262#13
Thanks, done. tc39/ecma262#13
This seems less useful than adding %IteratorPrototype%.map
and %IteratorPrototype.filter
. It also clarifies some of the confusion about whether you are changing the values, keys, or entries (since you would need to specify explicitly).
Unless I'm missing something (quite possible!), I would prefer not to add new methods to Map and Set when they could be added to %IteratorPrototype%
.
Yeah, I'm missing something. It's the difference in code between:
var newMap = oldMap.map(([k, v]) => [k + 1, v + 1]);
versus
var newMap = new Map(oldMap.entries().map((([k, v]) => [k + 1, v + 1]);
I think I still prefer avoiding every iterable subclass adding its own map/filter/etc. in favor of people using the compositional base primitives, but at least I see the argument now.
We already have Map#forEach
, and correlation with Array#forEach
->
Array#map
makes this constancy intuitive.
The only thing which Rick mentioned, is that Map#map
should probably
return a transformed [key, value]
pair, that allows updating the key as
well. However, need to think about it, since as mentioned on the diff [1],
usually people transform only values, and to force them always return an
array may be annoying (the key is not transformed in this case), and less
performant in terms useless array allocation.
This case I'd probably make as Map#mapWithKey
, however there other
questions arise like, should the returned pair replace the existing, or
create a new one, if the key is different?
({a => 10}).mapWithKey((k, v) => ['b', 20]); // {b => 20} or {a => 20, b =>
20} ?
But this is for further discussion.
var newMap = oldMap.map(([k, v]) => [k+ 1, v+ 1]);
versus
var newMap = new Map(oldMap.entries().map((([k, v]) => [k+ 1, v+ 1]);
But entries returns an iterator, and if we define %IteratorPrototype%
to
create a new instance of the right class given its |this|
parameter, in
this case Map
and not MapIterator
, then no need for the new Map()
wrapper.
I think I still prefer avoiding every iterable subclass adding its own map/filter/etc.
I point out in www.w3.org/Bugs/Public/show_bug.cgi?id=26973#c3
that the lack of Map.prototype.{map,filter,...}
doesn't bite when you
use for-of, but of course will be missed when you want to call
someMap.filter
directly. Which is reasonable to do!
({a => 10}).mapWithKey((k, v) => ['b', 20]); // {b => 20} or {a => 20, b => 20} ?
Whoops, nevermind actually, since we don't mutate the original map (the
this
), but rather return a new one, it should always be just {b => 20}
,
and a
key is ignored in the transformed map.
Dmitry
From: Brendan Eich [mailto:brendan at mozilla.org]
But entries returns an iterator, and if we define %IteratorPrototype% to create a new instance of the right class given its |this| parameter, in this case Map and not MapIterator, then no need for the new Map() wrapper.
This seems like it'd involve too much machinery and give unexpected results.
I'd expect instance.map(...)
to return another one of instance
. That is, given a MapIterator i
, I'd expect i.map(...)
to be another MapIterator, and not a Map like you are proposing.
Giving a MapIterator also has the advantage of preserving laziness. I like how in
var newMap = new Map(oldMap.entries().map((([k, v]) => [k + 1, v+ 1]);
The Map constructor call gives a clear delineation of where the iterator is being consumed, and the laziness is flattened down. Whereas if .entries().map(...)
gave a Map instance straightaway, the option of keeping things lazy would be gone.
This seems like it'd involve too much machinery and give unexpected results.
I'd expect
instance.map(...)
to return another one ofinstance
. That is, given a MapIteratori
, I'd expecti.map(...)
to be another MapIterator, and not a Map like you are proposing.
Right, I was smoking something bad. The uniformity you describe is clearly the right solution.
This means Map.prototype.map
etc. -- or %ItereablePrototype%.map
if not
Array.prototype.map
etc. -- are the eager collection-generic companions.
Bear with me, trying to develop a half-thought. To say it with code:
Object.defineProperty(%IteratorPrototype%, 'map', {
value: function (fun) {
return function* (iter) {
for (let [key, val] of iter) {
yield fun(val, key);
}
}(this.clone());
},
configurable: true,
enumerable: false,
writable: true
});
Note the this.clone()
call. This is novel, and required to avoid exhausting the receiver iterator.
Rather than %IterablePrototype%
, which is too generic a name (it does
not imply a key/value map), we need something such as %MaplikePrototype%
or %CollectionPrototype%
:
Object.defineProperty(%CollectionPrototype%, 'map', {
value: function (fun) {
let result = new this.constructor();
for (let [key, val] of this) {
result.set(key, val);
}
return result;
},
configurable: true,
enumerable: false,
writable: true
});
The Collection protocol thus consists of at least .constructor
,
@@iterator
, .set
. (Is there a better way to clone? Don't want a new
protocol where an old one will suffice!) This Collection protocol would
include Map and maplikes but leaves out Set, Array, Object -- appropriately.
We could just use FP-style map, filter, etc. APIs, but as argued among TC39ers, OOP wins in JS: (a) it matches built-ins; (b) it composes left to right, not inside out.
This argument implies a %CollectionPrototype%
object full of
protocol-generic methods, to avoid rewriting collection methods all over
the place (wherever there is a maplike), or borrowing Map.prototype.*
references by hand and monkeypatching them into maplike prototypes.
Unless I'm mistaken, the OOP-ness also implies other stuff, like a way to clone an iterator.
I hope this makes sense. Comments welcome.
Caffeinating still, obvious bugfix below:
Brendan Eich wrote:
result.set(key, val);
result.set(key, fun(val, key));
I call the mapfun in these examples with (val, key) -- could use the e-i-c convention from the Array extras but I was in a hurry (wherefore this bug).
Note the this.clone() call. This is novel, and required to avoid exhausting the receiver iterator.
This is noteworthy and worth keeping in mind regardless of the rest of the discussion. I think I would be fine having %IterablePrototype%.map exhaust the iterator. I would .clone()
manually myself if I didn't want that. But in general anytime I loop through the iterator---either directly with for
-of
, or indirectly with .map
and friends---I would expect exhaustion.
We could just use FP-style map, filter, etc. APIs, but as argued among TC39ers, OOP wins in JS: (a) it matches built-ins; (b) it composes left to right, not inside out.
I am still hoping the bind operator gives us a good middle ground. E.g.
import { map } from "js/iterables";
var newMap = oldMap::map(([k, v]) => [k + 1, v + 1]);
Here I guess map
would look something like
function map(...args) {
return new this.constructor(this.entries().map(...args));
}
(Note that I use new this.constructor(entriesIterable)
instead of your var m = new this.constructor()
plus m.set(...)
. Unclear which is better.)
Comments welcome.
My first thought is that it seems a little far out there given that we're talking about the net benefit being reducing
var newMap = new Map(oldMap.entries().map((([k, v]) => [k + 1, v + 1]);
to
var newMap = oldMap.map(([k, v]) => [k + 1, v + 1]);
My second thought is that we might also want to consider approaching this from the perspective of traits, see e.g. an earlier discussion about Set.
But, collections do need some love, so it's good that we're thinking about this.
I would expect exhaustion.
For iteration, sure. For map or other functional (pure?!) APIs? Yikes.
If in ES7 we build up the duality between iterables and observables, add
for-on, pave the way toward FRP, then map
as a protocol (part) should
be pure with respect to its receiver, ISTM.
the bind operator
More elaborate constructor protocol, but if it's part of maplike, may be ok.
Problem is :: vs. just dot, and now you have two problems. I'm actually good with FP inside-out, could learn to love :: if we add it, but the main objection IMHO is two-problems. OOP means just dot.
My first thought is that it seems a little far out there
Fair, just trying to develop a thought on es-discuss. I'm not sure we should do anything in TC39 about this. I want future underscore.js libraries to tread those cowpaths we ultimately pave.
Also the ES7 iterable/observable duality and the DOM maplike demands combine to push for paving sooner. We should be minimalists as you suggest, though.
My second thought is that we might also want to consider approaching this from the perspective of traits
Traits are great -- we lost a live, championed proposal at some point on the road to ES6. Who will revive?
From: Brendan Eich [mailto:brendan at mozilla.org]
For iteration, sure. For map or other functional (pure?!) APIs? Yikes.
If in ES7 we build up the duality between iterables and observables, add for-on, pave the way toward FRP, then
map
as a protocol (part) should be pure with respect to its receiver, ISTM.
This is iterables vs. iterators IMO. Iterators are inherently stateful and I'd expect anything that uses them to consume them, even something named "map". Iterables of course would not be.
I agree. It's inherent in the python-like design we're using for iteration.
It is not so jarring in practice, even for people with the classical training to recognize what a farce it is. :) Many uses of itertools in python, though blatantly stateful in implementation and operation, still "feel" functional, because the iterator being consumed is a temporary, something like mydict.iterkeys(). The mutation isn't observable if there's no other reference to the iterator object.
Good to known (and I had heard this via Python community channels
already). Still feels wrong, given "name polymorphism" on map
. Perhaps
we just plow ahead.
Still seems like someone (at some layer) will want iter.clone(). Vouch?
Problem is :: vs. just dot, and now you have two problems. I'm actually good with FP inside-out, could learn to love :: if we add it, but the main objection IMHO is two-problems. OOP means just dot.
That reminds me:
esdiscuss.org/topic/merging-bind-syntax-with-relationships
I know, OOP wins and all, but the downside is that it puts all the burden onto TC39 for defining a complete iterator API. The double-colon is a bit nasty-looking, though... Maybe we should consider single-arrow?
For example:
import { slice, forEach } from "my-userland-iterator-lib";
gimmeIterator()->slice(10, 20)->forEach(x => console.log(x));
Or for private fields:
import { makePrivate } from "private-fields";
const _x = makePrivate();
const _y = makePrivate();
class Point {
constructor(x, y) {
this->_x = x;
this->_y = y;
}
}
In any case, I think a merged relationships (i.e. private fields) + binding mechanism would be a huge win for the language.
Coming form other languages, double colons is used as a "static" thing, although we don't need (?) this convention in ES6 classes, and I agree it looks ugly anyway.
I wonder if here :>
would work instead, because I'd personally find any
other usage of the thin arrow ->
confusing *
- I personally see
->
as a shortcut forfunction
Should the returned pair replace the existing, or create a new one, if the key is different?
Mapping produces a new object, so the answer to that is luckily trivial.
- I personally see
->
as a shortcut forfunction
I'm a C sympathizer, so I don't see it the quite the same, but I understand. Regardless of the token, I feel like a generalized ref_get/set mechanism would be great.
On Tue, Oct 7, 2014 at 8:51 AM, Kevin Smith <zenparsing at gmail.com> wrote:
Problem is :: vs. just dot, and now you have two problems. I'm actually good with FP inside-out, could learn to love :: if we add it, but the main objection IMHO is two-problems. OOP means just dot.
That reminds me:
Hi Kevin,
Somehow I had missed that post until now. Interesting! For the sake of clarity while examining alternatives, let's assume for the moment the following infix operators:
infix "::" for bind alone as in strawman:bind_operator
infix "@" for relationships alone, as in strawman:relationships
infix "->" for bind+relationships as you propose here.
My question is: If we have "->", how much need would there still be for "::" or "@"? If "->" can subsume the practical need for either or both of
the others, that would be interesting.
We should postpone any further bikeshedding on which token to use for which meaning until we understand better which ones we actually need.
My question is: If we have "->", how much need would there still be for "::" or "@"? If "->" can subsume the practical need for either or both of the others, that would be interesting.
That was the idea, but it's been a while since I went through that exercise. I need to review.
I think it's definitely worth discussing the generic collection prototype, however at the meantime, an updated version of the mapping function, here: gist.github.com/DmitrySoshnikov/a218700746b2d7a7d2c8
- Moved the mapping to the abstract
MapTransform
- Reused it for
Map#map
andMap#mapEntries
(which updates the keys as well)
On Mon, Oct 6, 2014 at 9:37 AM, Brendan Eich <brendan at mozilla.org> wrote:
Brendan Eich wrote:
This meansMap.prototype.mapetc. -- or %ItereablePrototype%.map if not Array.prototype.map etc. -- are the eager collection-generic companions.
Bear with me, trying to develop a half-thought. To say it with code:
Object.defineProperty(%IteratorPrototype%, 'map', { value: function (fun) { return function* (iter) { for (let [key, val] of iter) { yield fun(val, key); } }(this.clone()); }, configurable: true, enumerable: false, writable: true });
Note the this.clone() call. This is novel, and required to avoid exhausting the receiver iterator.
Rather than %IterablePrototype%, which is too generic a name (it does not imply a key/value map), we need something such as %MaplikePrototype% or %CollectionPrototype%:
Object.defineProperty(%CollectionPrototype%, 'map', { value: function (fun) { let result = new this.constructor(); for (let [key, val] of this) { result.set(key, val); } return result; }, configurable: true, enumerable: false, writable: true });
The Collection protocol thus consists of at least .constructor, @@iterator, .set. (Is there a better way to clone? Don't want a new protocol where an old one will suffice!) This Collection protocol would include Map and maplikes but leaves out Set, Array, Object -- appropriately.
As long as it can be generic enough for Map
, Set
and .. (what else?) --
I actually like this one. Can try specifying it in a separate diff and will
get back to you.
We could just use FP-style map, filter, etc. APIs, but as argued among TC39ers, OOP wins in JS: (a) it matches built-ins; (b) it composes left to right, not inside out.
This argument implies a %CollectionPrototype% object full of protocol-generic methods, to avoid rewriting collection methods all over the place (wherever there is a maplike), or borrowing Map.prototype.* references by hand and monkeypatching them into maplike prototypes.
Yeah.
Unless I'm mistaken, the OOP-ness also implies other stuff, like a way to clone an iterator.
I hope this makes sense. Comments welcome.
Yep, thanks!
Dmitry
Dmitry Soshnikov wrote:
The Collection protocol thus consists of at least .constructor, @@iterator, .set. (Is there a better way to clone? Don't want a new protocol where an old one will suffice!) This Collection protocol would include Map and maplikes but leaves out Set, Array, Object -- appropriately.
As long as it can be generic enough for
Map
,Set
Set methods include add, whereas Map has set. Set lacks a get method, whereas Map of course has one. So not the same protocol.
On Wed, Oct 8, 2014 at 3:09 PM, Brendan Eich <brendan at mozilla.org> wrote:
Dmitry Soshnikov wrote:
The Collection protocol thus consists of at least .constructor, @@iterator, .set. (Is there a better way to clone? Don't want a new protocol where an old one will suffice!) This Collection protocol would include Map and maplikes but leaves out Set, Array, Object -- appropriately.
As long as it can be generic enough for
Map
,Set
Set methods include add, whereas Map has set. Set lacks a get method, whereas Map of course has one. So not the same protocol.
I see, yeah, it's doable, and potentially they can be handled via abstract
operations with checking kind of a collection (IsSet
, IsMap
), and
setting needed entries (AddEntry
would cover set
for maps, and add
for Sets), and GetEntry
would return needed thing since sets are just
backed by maps. However, this seems not a big win in terms of constant
runtime checks for this, and probably having a separate algorithms are
better (even if some parts are repeated).
If you want to have just an explicit protocol/interface for "MapLike"s and
"SetLikes" (i.e. with any explicit set
method, and stuff which can be
implemented at user-level by any object), it's possible. Although, this
interface implementation can also be just replaced with sub-classing of the
Map
, and the same algorithm works. It's just a difference b/w duck-typing
(the protocol, that any object may implement regardless its hierarchy), or
the inheritance. Will think about it, and maybe will come up with a spec.
Dmitry
Is this something what is ready for discussion on a meeting (in particular, tomorrow's meeting)?
Regardless, whether the map
and set
will be directly on the
Map.prototype
or on some generic %CollectionPrototype%
, the user-level
API will stay the same, right? And will be used as:
myMap.map((v, k) => { ... });
I was just curious, is it already the time when it can be confirmed on a
meeting, that the API will likely be this (assuming we have map.forEach
already)? We'd like already to extend the Map
and Set
API (by extending
the ES6 base implementation) with several new methods, and start using them.
If this spec gist.github.com/DmitrySoshnikov/a218700746b2d7a7d2c8 is good enough to discuss it and can be confirmed, will it be a good time on the following meeting? This PR for agenda wasn't merged for almost a month: tc39/agendas#53
If it can be confirmed already now, and no actual meeting discussion is needed for this, I'm fine with this as well.
The only thing that may not confirm it, is using that new binding method
::
operator and iter tools:
import {map} form 'iter-tools'
map::map(...);
But this as I understand is not even in early stage of a proposal (and
again, having dependency/correlation on map.forEach
probably the map
and filter
will nevertheless be used as just map.map
).
P.S.: besides these two methods, maps and sets API is pretty basic at the moment and doesn't have many "every-day to use" which are available in other languages in these collections: like the most obvious "intersect" or "union" operations for sets, etc. However, we can start with "map" and "filter" yet ;)
Dmitry
Dmitry Soshnikov wrote:
Is this something what is ready for discussion on a meeting (in particular, tomorrow's meeting)?
You put something on the agenda, cool.
(For the history of this thread to refer to it later)
At the meeting it was decided not to go with map
and filter
sitting on
Map.prototype
, but instead to use iterators in the way like:
map
.entries() // returns an iterator
.map((v, k, m) => { ... })
.filter((v, k, m) => { ... })
.collect(); // returns a new map after all transforms
The forEach
method is the only which will still exist on Map.prototype
,
and the committee agreed it's fine.
At the same time, the forEach
can be called on iterator as well:
map
.entries()
.map(...)
.forEach(...);
Dmitry
On Wed, Nov 19, 2014 at 4:49 PM, Dmitry Soshnikov <dmitry.soshnikov at gmail.com> wrote:
(For the history of this thread to refer to it later)
At the meeting it was decided not to go with
map
andfilter
sitting onMap.prototype
, but instead to use iterators in the way like:map .entries() // returns an iterator .map((v, k, m) => { ... }) .filter((v, k, m) => { ... }) .collect(); // returns a new map after all transforms
Sounds good! (I always end up calling one of the iterators on python dicts anyway, since I can never remember what the default iterator behavior is.)
I presume that .collect() expects an iterator of [k,v] pairs or something? Also: this is the first I've heard of .collect(). I presume that'll show up in the spec?
At the meeting it was decided not to go with
map
andfilter
sitting onMap.prototype
, but instead to use iterators in the way like:map .entries() // returns an iterator .map((v, k, m) => { ... }) .filter((v, k, m) => { ... }) .collect(); // returns a new map after all transforms
Convenient, but this pattern couldn’t be extended to new Map classes or other collections. I see two alternatives.
First, use the Map
constructor. I don’t find this too bad and it’s self-explanatory.
new Map(map
.entries() // returns an iterator
.map((v, k, m) => { ... })
.filter((v, k, m) => { ... }));
Second:
map
.entries() // returns an iterator
.map((v, k, m) => { ... })
.filter((v, k, m) => { ... })
.collectInto(new Map());
collectInto(coll)
invokes coll.set(k,v)
for each pair [k,v]
in the sequence. It returns coll
.
Where can I read about the rationale to not put those methods on the Map prototype? I'm curious why that was okay for the Array class, but not okay for Map and Set.
On Thu, Nov 20, 2014 at 10:32 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
At the meeting it was decided not to go with
map
andfilter
sitting onMap.prototype
, but instead to use iterators in the way like:map .entries() // returns an iterator .map((v, k, m) => { ... }) .filter((v, k, m) => { ... }) .collect(); // returns a new map after all transforms
Convenient, but this pattern couldn’t be extended to new Map classes or other collections. I see two alternatives.
First, use the
Map
constructor. I don’t find this too bad and it’s self-explanatory.
new Map(map .entries() // returns an iterator .map((v, k, m) => { ... }) .filter((v, k, m) => { ... }));
Second:
map .entries() // returns an iterator .map((v, k, m) => { ... }) .filter((v, k, m) => { ... }) .collectInto(new Map());
collectInto(coll)
invokescoll.set(k,v)
for each pair[k,v]
in the sequence. It returnscoll
.
+1. This also gives us a pseudonym for Python's .update() operation,
which adds one dict into another, and which I use relatively often to
merge dictionaries together. Where Python would say m1.update(m2)
(m1 now contains all of m2's data), JS would say
m2.entries().collectInto(m1)
.
This is more powerful than just using the constructor, as it allows you to update a dict that already has information in it, and adding another function to a chain of transformations is slightly easier to do than wrapping it in a function call.
From: Tab Atkins Jr. [mailto:jackalmage at gmail.com]
Sounds good! (I always end up calling one of the iterators on python dicts anyway, since I can never remember what the default iterator behavior is.)
Yeah. There is a separate idea that we should probably define Map.prototype.{map, filter, etc.} as behaving the same way as the default iterator (which in this case is .entries()). There's a few ideas on how to do this, e.g. specialized Map methods, or put a "Collection prototype" between Map.prototype and Object.prototype that generically delegates to the default iterator, or...
I presume that .collect() expects an iterator of [k,v] pairs or something? Also: this is the first I've heard of .collect(). I presume that'll show up in the spec?
The idea of .collect() is that when the iterator is created, it's told how to collect; in this case it would be using new Map
I think. (Although there was talk of Map.from
at the meeting, which I guess would be an additional API.) Since the iterator in this example does yield [k, v] pairs, and new Map
accepts an iterable of [k, v]
pairs, this will work nicely.
But yes, right now %IteratorPrototype% is entirely empty as just a placeholder for ES6. It needs map, filter, forEach, reduce, as well as collect. Design work to be done :)
On 20 Nov 2014, at 1:49 , Dmitry Soshnikov <dmitry.soshnikov at gmail.com> wrote:
(For the history of this thread to refer to it later)
At the meeting it was decided not to go with
map
andfilter
sitting onMap.prototype
, but instead to use iterators in the way like:map .entries() // returns an iterator .map((v, k, m) => { ... }) .filter((v, k, m) => { ... }) .collect(); // returns a new map after all transforms
Question – shouldn’t this example be written as follows?
map
.entries() // returns an iterator
.map(([k,v], i, m) => { ... })
.filter(([k,v], i, m) => { ... })
.collect(); // returns a new map after all transforms
Otherwise, I suggest to change the names (e.g. to mapPairs
and filterPairs
).
From: Axel Rauschmayer [mailto:axel at rauschma.de]
Question – shouldn’t this example be written as follows?
Yes.
On Thu, Nov 20, 2014 at 10:42 AM, Domenic Denicola <d at domenic.me> wrote:
From: Tab Atkins Jr. [mailto:jackalmage at gmail.com]
I presume that .collect() expects an iterator of [k,v] pairs or something? Also: this is the first I've heard of .collect(). I presume that'll show up in the spec?
The idea of .collect() is that when the iterator is created, it's told how to collect; in this case it would be using
new Map
I think.
Interesting idea, but I'm not sure how it's supposed to work in practice. Is this metadata exposed somehow, so you can pass it along to new iterators you create in custom iterator-algebra methods? How does this work with iterator-algebra methods implemented as generators?
From: Tab Atkins Jr. [mailto:jackalmage at gmail.com]
Interesting idea, but I'm not sure how it's supposed to work in practice.
Doesn't answer all of your questions, and is probably half-assed in various ways, but here is some stuff that we came up with in some live-coding during the meeting:
Map.prototype.entries = function () {
return new MapIterator({ collectAs: this.constructor[Symbol.species] });
};
class MapIterator extends Iterator {
constructor({ collectAs }) {
super();
this[Symbol.collectAs] = collectAs;
}
}
Iterator.prototype.collect = function () {
return this[Symbol.collectAs].from(...);
};
myMap.entries() // proposal: you can remove .entries()
.filter(([k, v]) => k % 2 === 0)
.map(([k, v]) => [k * 2, v])
.forEach(...);
// or .reduce(...) also forces
// or for-of
// or .collectAs(Array)
// or .collect(), which uses [Symbol.collectAs] as a default
On Thu 20 Nov 2014 01:49, Dmitry Soshnikov <dmitry.soshnikov at gmail.com> writes:
At the meeting it was decided not to go with
map
andfilter
sitting onMap.prototype
, but instead to use iterators in the way like:map .entries() // returns an iterator .map((v, k, m) => { ... }) .filter((v, k, m) => { ... }) .collect(); // returns a new map after all transforms
Is there a nice way to do this for the default iterator? This is pretty terrible:
[1,2,3][Symbol.iterator]().map(x=>x+1)
Also, I thought that map, filter, and such were to be on the Iterator.prototype. That precludes multi-arg map functions, doesn't it?
From: Andy Wingo [mailto:wingo at igalia.com]
Is there a nice way to do this for the default iterator?
esdiscuss.org/topic/map-filter-map-and-more#content-57
Also, I thought that map, filter, and such were to be on the Iterator.prototype. That precludes multi-arg map functions, doesn't it?
(Maps are awesome!)
1) Transforming iteration methods
We're currently polyfillying the
Map
, and got some questions from devs. One of them is about transforming iteration methods, likemap
andfilter
.Unfortunately I missed that part of the spec when it was approved, so can someone please remind/clarify -- was it an intended decision not to have
Map#map
,Map#filter
? I can see onlyMap#forEach
in the spec. Are maps "immutable"? -- That's fine, themap
andfilter
return a new map.2) Declarative syntax
The other thing to note: currently maps a lack of a nice declarative syntax. This one came from the use-case for maps for dynamic (computed) property names.
Previously, we had to allocate an empty object, and then, in the imperative style, append needed props:
var requestData = {}; requestData[Names.ID] = id; requestData[Names.EMAIL] = email; requestData.needsReload = true; ... new Request(...) .setData(requestData) .send();
With computed properties of object initialisers it's much simpler and convenient:
new Request(...) .setData({ [Names.ID]: id, [Names.EMAIL]: email, needsReload: true, }) .send();
Then thing is: if we'd like to use maps for such use-case, it brings us back to that inconvenient imperative style of assignments (even worse, since you have to repeat that
.set(...)
constantly):var requestData = new Map(); requestData.set(Names.ID, id); requestData.set(Names.EMAIL, email); requestData.set('needsReload', id); ...
Yes, we provide the iterable option for the constructor, and it can be rewritten as (and this can even be inlined):
var requestData = new Map([ [Names.ID, id], [Names.EMAIL, email], ['needsReload', id] ]);
However, obviously, it's too many arrays allocation for such a simple use case.
Will it make sense having a nice declarative syntax like:
new Map({ [Names.ID]: id, [Names.EMAIL]: email, needsReload: true, })
It can even be done via a simple helper method that transforms this object literal with computed props to the same iterable array of array, but this, unfortunately, doesn't work with all cases like "objects as keys".
I don't have the exact idea yet of how such a syntax can look like, but it seems would be nice to have.
(unless, the use-case is not for maps, and we should use simple objects here as was shown above)