Skip to content

flatMap typescript type for Keyed, [Ordered]Map are not substitutable for Collection.flatMap #1283

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
sandersn opened this issue Aug 8, 2017 · 1 comment

Comments

@sandersn
Copy link

sandersn commented Aug 8, 2017

I'm getting immutable.d.ts to work with Typescript 2.4 right now. The 2.4 compiler finds several new bugs in the types. The worst bug is that Keyed.flatMap (and its descendants' flatMaps) are not substitutable for the type of Collection.flatMap. This type error is reflected at runtime:

> var i = require('immutable')
> var s = i.Set([1,2,3])
> var l = i.List([1,2,3])
> var m = i.Map([['a', 1], ['m', 2]])
> var cols = [s, m, l]
> cols.map(col => col.flatMap((v, k) => [v, v, v, v, v, v]))
TypeError: Expected [K, V] tuple: 1
    at validateEntry (/home/nathansa/src/test/node_modules/immutable/dist/immutable.js:3577:13)
    at /home/nathansa/src/test/node_modules/immutable/dist/immutable.js:2924:11
    at ArraySeq.__iterate (/home/nathansa/src/test/node_modules/immutable/dist/immutable.js:375:13)
    at FromEntriesSequence.__iterate
// ... snip some error output ...
> cols.map(col => col.flatMap((v, k) => [[k, v], [k, v]]))
[ Set { 1,1, 1,1, 2,2, 2,2, 3,3, 3,3 },
  Map { "a": 1, "m": 2 },
  List [ 0,1, 0,1, 1,2, 1,2, 2,3, 2,3 ] ]

As you can see, Map.flatMap expects its mapper to return a list of pairs. The other flatMaps expect their mappers to return a list of items. Mixing the two collection types in a list produces either a TypeError or unsatisfactory results.

This mismatch is reflected in the typings in immutable.d.ts:

export interface Keyed<K, V> extends Collection<K, V> {
  // ...
  flatMap<KM, VM>(mapper: (value: V, key: K, iter: this) => Iterable<[KM, VM]>, context?: any): Collection.Keyed<KM, VM>;
  // ...
}
export interface Collection<K, V> extends ValueObject {
  // ...
  flatMap<M>(mapper: (value: V, key: K, iter: this) => Iterable<M>, context?: any): Collection<K, M>;
  // ...
}

The Keyed.flatMap says that mapper is allowed to change the key type and the value type, but Collection.keyMap says that mapper is only allowed to change the value type.

Fixes

I have thought of a few different ways to fix this:

  1. Change implementations of Keyed.flatMap to match other flatMaps. (and the type as well)
  2. Ignore the type difference: add an overload that's just a copy of Collection.flatMap. This fools the compiler.
  3. Ignore the type difference: have Keyed.flatMap return Keyed<any, any> instead of Keyed<KM, VM>.
  4. Break the type relationship: Remove extends Collection<K, V> from interface Keyed<K, V> extends Collection<K, V>.

(1) is surely a breaking change. (2) is incorrect and allows too many types as arguments. (3) is vague and allows too many types as arguments. (4) is a breaking change that would harm the usability of the library as whole. However, without some kind of fix, the additional checks provided by Typescript 2.4 will have to be hidden behind the flag --noStrictGenericChecks, and this flag will have to be provided by all users of immutable.

@leebyron
Copy link
Collaborator

leebyron commented Oct 4, 2017

Thanks for your help fixing this, @sandersn!

@leebyron leebyron closed this as completed Oct 4, 2017
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