Skip to content
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

Fix the TypeScript overloading for map #401

Merged
merged 1 commit into from
Nov 19, 2019
Merged

Fix the TypeScript overloading for map #401

merged 1 commit into from
Nov 19, 2019

Conversation

Avaq
Copy link
Member

@Avaq Avaq commented Nov 18, 2019

To close #400

I'm planning to release this immediately as a patch.

/cc @gcanti

@Avaq Avaq requested a review from dicearr November 18, 2019 17:14
@Avaq Avaq self-assigned this Nov 18, 2019
@codecov
Copy link

codecov bot commented Nov 18, 2019

Codecov Report

Merging #401 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #401   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          45     45           
  Lines        1896   1896           
=====================================
  Hits         1896   1896

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b9701a8...0e28c8f. Read the comment docs.

@Avaq
Copy link
Member Author

Avaq commented Nov 18, 2019

That last force-push was to add you as co-author @gcanti -- I felt bad taking credit after copy-pasting the code from #400 ;)

@Avaq Avaq merged commit 9a5106a into master Nov 19, 2019
@Avaq Avaq deleted the avaq/fix-map-type branch November 19, 2019 11:19
wms added a commit to wms/Fluture that referenced this pull request Dec 4, 2019
Although fluture-js#401 solved the case for using `map` on a `ConcurrentFutureInstance`, it broke `FutureInstance#pipe(map(...))`:

```typescript
import { ConcurrentFutureInstance, FutureInstance, map } from 'fluture';

declare const x: FutureInstance<Error, number>;
declare const y: ConcurrentFutureInstance<Error, number>;
declare const double: (x: number) => number;

const v1 = map(double)(x); // ok; FutureInstance<Error, number>
const v1p = map(double)(y); // ok; ConcurrentFutureInstance<Error, number>
const v2 = x.pipe(map(double)) // error
```

This could potentially be down to TypeScript selecting the wrong overload (maybe because `ConcurrentFutureInstance` is the more restrictive type?)

So, I came up with this version instead, which uses a Conditional type to represent the mapper function instead. It's not ideal from an ergonomics perspective, as you end up with the whole conditional type appearing in Intellisense information. I've never been fond of that. But, it does fix (and preserve all type information) for the three forms above:

```typescript
import { ConcurrentFutureInstance, FutureInstance, /*map*/ } from 'fluture';

declare function map<RA, RB>(mapper: (value: RA) => RB): <T>(source: T) =>
    T extends FutureInstance<infer U, infer V> ?
    FutureInstance<U, V> :
    T extends ConcurrentFutureInstance<infer U, infer V> ?
    ConcurrentFutureInstance<U, V> :
    never;

declare const x: FutureInstance<Error, number>;
declare const y: ConcurrentFutureInstance<Error, number>;
declare const double: (x: number) => number;

const v1 = map(double)(x); // ok; FutureInstance<Error, number>
const v1p = map(double)(y); // ok; ConcurrentFutureInstance<Error, number>
const v2 = x.pipe(map(double)) // ok; FutureInstance<Error, number>
```

I did also experiment with simply swapping the order of the overloads around:

```typescript
import { ConcurrentFutureInstance, FutureInstance, /*map*/ } from 'fluture';

declare function map<RA, RB>(mapper: (value: RA) => RB): {
    <L>(source: ConcurrentFutureInstance<L, RA>): ConcurrentFutureInstance<L, RB>;
    <L>(source: FutureInstance<L, RA>): FutureInstance<L, RB>;
}

declare const x: FutureInstance<Error, number>;
declare const y: ConcurrentFutureInstance<Error, number>;
declare const double: (x: number) => number;

const v1 = map(double)(x); // ok; FutureInstance<Error, number>
const v1p = map(double)(y); // ok; ConcurrentFutureInstance<Error, number>
const v2 = x.pipe(map(double)) // almost ok; FutureInstance<unknown, number>
```

Observe that the left type of `v2` is `unknown` when it should be `Error`. I suspect this is down to that hanging `<L>` type parameter, and the only way to eliminate that (afaik) is to use a conditional type instead. Which, coincidentally, is where we came in.
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

Successfully merging this pull request may close these issues.

TypeScript doesn't pick the correct overloading when mapping a ConcurrentFutureInstance (v12.0.0)
3 participants