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

Type inference for piped arrow functions #22081

Closed
ivan7237d opened this issue Feb 21, 2018 · 10 comments
Closed

Type inference for piped arrow functions #22081

ivan7237d opened this issue Feb 21, 2018 · 10 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@ivan7237d
Copy link

TypeScript Version: 2.7.1 (the issue occurs independently of whether the strict mode, including strictFunctionTypes, is turned on)

Search Terms: pipe, type inference, generic

Code/actual behavior:

const pipe = <A, B, C>(
  x: A,
  a: (x: A) => B, 
  b: (x: B) => C,
) => b(a(x));

// This just calls the function passed as argument. In
// other words, it's an identity function that takes
// an argument of a specific type (x: A) => B.
const call = <A, B>(f: (x: A) => B) => (x: A) => f(x)

// For this line, TS can't infer type of the last argument
// and addition produces error.
const a = pipe(1, x => x + 1, call(x => x + 1));
// But it successfully infers types for this line where
// the second argument is wrapped in call(...)
const b = pipe(1, call(x => x + 1), call(x => x + 1));

Expected behavior:
Typescript should be able to infer the types for the simpler one of the these two lines if it can infer them for the more complex one.

Playground Link:
Playground

Related Issues:
This issue originates from discussion here #22051.

@sylvanaar
Copy link

sylvanaar commented Feb 26, 2018

I really think this is the old problem about having 2 inference sites for the same type variable. Let me try to find the issue.

Here you go: #14829

Here is your code with the fix suggested in that issue:

export type NoInfer<T> = T & {[K in keyof T]: T[K]};

const pipe = <A, B, C>(
  x: A,
  a: (x: A) => B, 
  b: (x: NoInfer<B>) => C,
) => b(a(x));


const call = <A, B>(f: (x: A) => B) => (x: A) => f(x)

const a = pipe(1, x => x + 1, call(x => x + 1));
const b = pipe(1, call(x => x + 1), call(x => x + 1));

@ivan7237d
Copy link
Author

ivan7237d commented Feb 27, 2018

@sylvanaar Interesting, thanks!

Unfortunately that fix doesn't seem to work with strictFunctionTypes.

Another thing is it seems to me that the NoInfer feature is intended for cases when TypeScript can successfully infer the type on both of the sites, and you want to tell it to prefer one site over another, but not for the cases when it can infer the type on one site but can't infer it on the other site.

If it helps, this is what I think of as a typical use case for NoInfer:

const defaultValue = <T>(source: T | undefined, defaultValue: NoInfer<T>) =>
  source === undefined ? defaultValue : source;
type A = { a?: number };
// Typescript can infer the type from the second argument, but you don't
// want it to do it because then .a would be causing an error.
const f = (arg: A | undefined) => defaultValue(arg, {}).a;

@sylvanaar
Copy link

sylvanaar commented Feb 27, 2018

Just trying to explain to you what is happening. Inference is occurring at the site with the unbound type variables, so you end up inferring {} for B.

This only happens if you mix supplying functions with unbound type variables and functions without them as arguments for some reason.

const a = pipe(1, x => x + 1, x => x + 1); works just fine.

It seems like the type substitution is happening in multiple phases, and seems to prefer inferring the type from what you passed to call instead of treating what you passed as a function with unbound type parameters. Anyways - let's see what MS has to say.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Jul 19, 2018
@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@ivan7237d
Copy link
Author

@mhegazy Not sure if it's a good idea to close this issue — right now when you're using pipes, you often have to use a counterintuitive call() trick I described in this stackoverflow answer to get type inference to work. And it's not a rare use case — in rxjs for example, it's very common to have pipes + arrow functions in code like observable.pipe(source => merge(source, otherObservable)).

@americanslon
Copy link

Any news on this? I just updated from angular 5 to 6, which entails rxjs v6+ and I have this issue all over my project. Surprised there aren't more complaints out there seeing how popular angular is.

@topaxi
Copy link

topaxi commented Aug 15, 2018

This is quite annoying and should not be closed.

@cartant
Copy link

cartant commented Aug 15, 2018

My understanding is that @mhegazy is "moving on to new adventures". Should this be closed, @RyanCavanaugh ?

@RyanCavanaugh
Copy link
Member

This is a limitation of the current inference algorithm. We don't have a good way to know that inference in this function is supposed to proceed left-to-right and that the inference needs to happen in two discrete steps (first resolving B, then C).

@RyanCavanaugh
Copy link
Member

See also #25826

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

8 participants