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

Order of parameters in curried function should not affect inference #16914

Closed
bcherny opened this issue Jul 3, 2017 · 3 comments
Closed

Order of parameters in curried function should not affect inference #16914

bcherny opened this issue Jul 3, 2017 · 3 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@bcherny
Copy link

bcherny commented Jul 3, 2017

// helpers
let concat = <T>(a: T[]) => (b: T[]) => [...a, ...b]
let tail = <T>([_, ...t]: T[]) => t

// (case 1) curried
let map = <T, U>(fn: (a: T) => U) => (xs: T[]): U[] => {
  switch (xs.length) {
    case 0: return []
    default: return concat([fn(xs[0])])(map(fn)(tail(xs)))
  }
}
map(_ => _ * 2)([1, 2, 3]) // BAD - T is inferred as {}

// (case 2) curried (reverse)
let map2 = <T, U>(xs: T[]) => (fn: (a: T) => U): U[] => {
  switch (xs.length) {
    case 0: return []
    default: return concat([fn(xs[0])])(map2(tail(xs))(fn)) // BAD - map2(..) inferred as {} instead of U[]
  }
}
map2([1, 2, 3])(_ => _ * 2) // GOOD - T is inferred as number

// (case 3) not curried
let map3 = <T, U>(fn: (a: T) => U, xs: T[]): U[] => {
  switch (xs.length) {
    case 0: return []
    default: return concat([fn(xs[0])])(map3(fn, tail(xs)))
  }
}
map3(_ => _ * 2, [1, 2, 3]) // GOOD - T is inferred as number
  • In (1), after I pass an array of numbers as the second argument, T should be fixed at number.
  • In (2), the return type of map2's recursive call is inferred incorrectly.
  • (3) works as expected.

On a side note, I'm not sure why the return type annotation : U[] is necessary. Without it, the return types of all 3 functions are inferred as any.

Filing all of these as one issue, but I am more than happy to split these out after you guys get a chance to triage.

Related:

@ikatyang
Copy link
Contributor

ikatyang commented Jul 4, 2017

For case 1, TS has no idea what the type should _ be, since there is no more information for _, the only thing TS know is that it will return number ( * ).


Generics won't be inferred across functions, that is, generics won't be changed once the type parameter was determined.

Case 2 should be something like this:

-let map2 = <T, U>(xs: T[]) => (fn: (a: T) => U): U[] => {
+let map2 = <T>(xs: T[]) => <U>(fn: (a: T) => U): U[] => {

FYI, this is my map declaration.


On a side note, I'm not sure why the return type annotation : U[] is necessary. Without it, the return types of all 3 functions are inferred as any.

I think it might be some limitations for recursive function?


The FP declaration/inference is really a big challenge to TS, since TS can't infer types across functions. I've tried a lot for Ramda declarations and finally realized that there are a lot of limitations for curried inference.

@bcherny
Copy link
Author

bcherny commented Jul 4, 2017

Case 2 should be something like this ...

Excellent - that works perfectly.

It seems that as you said, the current behavior is that once a generic is resolved to a type, it will not change. I wonder if a better behavior would be to refine the generic type as more information comes in, similar to what TS already does for non-generic inferred types. Eg. for the first example:

let a = map(_ => _ * 2) // T is inferred as any/{}
let b = a([1, 2, 3]) // T is refined to number (because number is assignable to any/{})

@mhegazy mhegazy added the Needs Investigation This issue needs a team member to investigate its status. label Aug 29, 2017
@RyanCavanaugh RyanCavanaugh added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Needs Investigation This issue needs a team member to investigate its status. labels Jul 23, 2019
@RyanCavanaugh
Copy link
Member

Tracking at #30134 since the current inference algorithm is not capable of making 100% order-agnostic inferences

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants