Thanks to visit codestin.com
Credit goes to github.com

Skip to content

toMap() should take optional key/value selector functions #697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dubrowgn opened this issue Nov 12, 2015 · 11 comments
Closed

toMap() should take optional key/value selector functions #697

dubrowgn opened this issue Nov 12, 2015 · 11 comments

Comments

@dubrowgn
Copy link

Common use case for easily indexing a sequence of data. It would basically work like this:

let data = Immutable.List.of(
    { id:1, value:"value 1" },
    { id:2, value:"value 2" },
    { id:3, value:"value 3" }
);

let mappedObjects = data.toMap((item) => item.id);
let mappedValues = data.toMap((item) => item.id, (item) => item.value);

Currently you would probably use reduce:

let data = Immutable.List.of(
    { id:1, value:"value 1" },
    { id:2, value:"value 2" },
    { id:3, value:"value 3" }
);

let mappedObjects = data.reduce((map, item) => map.set(item.id, item), Immutable.Map());
let mappedValues = data.reduce((map, item) => map.set(item.id, item.value), Immutable.Map());
@dubrowgn
Copy link
Author

Related: #600

@dubrowgn
Copy link
Author

I started making a pull request, but there appear to be failing tests on master at the moment. This is the code I came up with to replace toMap().

toMap(keySelector, valueSelector) {
  // Use Late Binding here to solve the circular dependency.

  // build a reducer that takes selector functions into account
  var reducer = null;
  if (keySelector && valueSelector)
    reducer = (m, v, k) => m.set(keySelector(v, k), valueSelector(v, k));
  else if (keySelector)
    reducer = (m, v, k) => m.set(keySelector(v, k), v);
  else if (valueSelector)
    reducer = (m, v, k) => m.set(k, valueSelector(v, k));

  // reduce required?
  if (reducer === null)
    return Map(this.toKeyedSeq());

  // apply selector functions via reducer
  return this.reduce(reducer, Map());
}

@leebyron
Copy link
Collaborator

This convenience method is going to be removed in a future version of Immutable as it can be confusing and currently restricts the ability to do incremental builds of Immutable. e.g. instead of doing: myThing.toMap() you would write Map(myThing). For this reason I think we would not want to consider expanding the API to perform more tasks.

This also means if you would like a mapping function to construct the Map, you could write: Map(myThing.map(a => [b, c])) which is hopefully less confusing (people often confuse the map function and the Map type)

@dubrowgn
Copy link
Author

Does Map(myThing.map(a => [b, c])) work currently?

I don't see how toMap is confusing, although I can see how it presents a problem for modularity. If all the transform functions (ie map()) returned a generic Seq or Iterable like they used to, this wouldn't be a problem at all. The Map module could have easily extended the prototype of Seq to include a definition for toMap().

Removing toMap() and family hurts chainability, since the constructor must come first. Thus, if you have a longer chain of transformations with a concrete data type in the middle, you have to bounce back and forth as you read the code, leading to reduced readability and comprehension.

@leebyron
Copy link
Collaborator

Yep, I agree with you. What we're looking at is a tradeoff between chainability and modularity. Currently the library optimizes for chainability, but that means modularity isn't possible. As there's simultaneous demand for adding new kinds of data structures while also reducing the size of the library, we're stuck until the modularity problem is solved, and unfortunately that means giving up some chainability.

Luckily, after surveying a large body of code that uses Immutable, conversion between different types is far from the most common of operations, so this should have relatively small impact on code quality and readability.

@leebyron
Copy link
Collaborator

Does Map(myThing.map(a => [b, c])) work currently?

It does!

var list = Immutable.List([ 1, 2, 3 ]);
var map = Immutable.Map(list.map(v => [ v, v ]));
map.toString();
"Map { 1: 1, 2: 2, 3: 3 }"

@dubrowgn
Copy link
Author

I think there is something wrong with the map() function then. This is what I tried before:

var m = Immutable.Map({ "10":100, "20":200 }).map((v, k) => [parseInt(k), v]);
m.toString();
// "Map { "10": 10,100, "20": 20,200 }"

@dubrowgn
Copy link
Author

Luckily, after surveying a large body of code that uses Immutable, conversion between different types is far from the most common of operations, so this should have relatively small impact on code quality and readability.

This issue and issues like it show we think converting between data types should less painful, but it looks like the project is going in the opposite direction.

@leebyron
Copy link
Collaborator

I'm sorry you feel that way @dubrowgn, my hope is to balance lots of different concerns for a best possible outcome. I understand that means tradeoffs in some places in order to accomplish other things. I certainly am not trying to actively move in a direction opposite from where issues are trying to highlight, on the contrary I'm trying to move in a direction that can satisfy as many as possible.

Here I'm just trying to highlight that it's good news that your proposed code:

data.toMap((item) => item.id, (item) => item.value);

Can be written today as:

Map(data.map(item => [ item.id, item.value ]));

And that is both compatible with where the library's API is going in the future, and I hope is also not too detrimental to code legibility.

@leebyron
Copy link
Collaborator

I think there is something wrong with the map() function then. This is what I tried before:

var m = Immutable.Map({ "10":100, "20":200 }).map((v, k) => [parseInt(k), v]);
m.toString();
// "Map { "10": 10,100, "20": 20,200 }"

My assumption with the example code above was that you were converting from a List to a Map. If you already have a Map, then at least we're beyond the question of conversion.

The .map() function on a Map type maps over the values, maintaining keys. If you want to map over each entry as a separate element, then you should check out .mapEntries() which let's you write:

var m = Immutable.Map({ "10":100, "20":200 }).mapEntries(([k, v]) => [parseInt(k), v]);
m.toString();
// "Map { 10: 100, 20: 200 }"

@dubrowgn
Copy link
Author

Good to know, thanks!

When using JSON to encode maps, int keys always come out as strings, so this kind of Map to Map conversion happens pretty regularly in the project I am working on.

It seems strange that map() would behave differently depending on the source data type. The docs for map() don't mention this either. Given that map() always provides a key-value pair to the mapper function anyway, the distinction between Iterable.map and KeyedIterable.map is surprising to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants