Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Smarter type inference for functional programming (RxJS, Ramda, etc) #15680

Copy link
Copy link
@benlesh

Description

@benlesh
Issue body actions

EDIT: updated the types in example to match what works perfectly in Flow type

TypeScript Version: 2.2.1 / nightly (2.2.0-dev.201xxxxx)

TypeScript is unable to infer types through higher-order functions.

Code

Given some imaginary class SetOf, which is just some set of values we want to compose operations over... We'll try the following:

// This is a contrived class. We could do the same thing with Observables, etc.
class SetOf<A> {
  _store: A[];

  add(a: A) {
    this._store.push(a);
  }

  transform<B>(transformer: (a: SetOf<A>) => SetOf<B>): SetOf<B> {
    return transformer(this);
  }

  forEach(fn: (a: A, index: number) => void) {
      this._store.forEach((a, i) => fn(a, i));
  }
}

function compose<A, B, C, D, E>(
  fnA: (a: SetOf<A>) => SetOf<B>, 
  fnB: (b: SetOf<B>) => SetOf<C>, 
  fnC: (c: SetOf<C>) => SetOf<D>,
  fnD: (c: SetOf<D>) => SetOf<E>,
):(x: SetOf<A>) => SetOf<E>;
/* ... etc ... */
function compose<T>(...fns: ((x: T) => T)[]): (x: T) => T {
  return (x: T) => fns.reduce((prev, fn) => fn(prev), x);
}

function map<A, B>(fn: (a: A) => B): (s: SetOf<A>) => SetOf<B> {
  return (a: SetOf<A>) => {
    const b: SetOf<B> = new SetOf();
    a.forEach(x => b.add(fn(x)));
    return b;
  }
}

function filter<A>(predicate: (a: A) => boolean): (s: SetOf<A>) => SetOf<A> {
  return (a: SetOf<A>) => {
    const result = new SetOf<A>();
    a.forEach(x => {
      if (predicate(x)) result.add(x);
    });
   return result;
  }
}

const testSet = new SetOf();
testSet.add(1);
testSet.add(2);
testSet.add(3);

// THE PROBLEM IS HERE
// all functions below are unable to infer any types.
// The user will be required to annotate the types manually at each step.
testSet.transform(
  compose(
    filter(x => x % 1 === 0),
    map(x => x + x),
    map(x => x + '!!!'),
    map(x => x.toUpperCase())
  )
)

testSet.transform(
  compose(
    filter(x => x % 1 === 0),
    map(x => x + x),
    map(x => 123),  // Whoops a bug
    map(x => x.toUpperCase()) // causes an error!
  )
)

Libraries Where This Problem Will Exist

RxJS

We want to move RxJS over to using "lettable operators". That means operators that are built in a similar fashion to what you see in the example able. The current prototype augmentation is untenable for large applications, and makes tree-shaking hard if not impossible with regards to Rx.

Ramda

A widely popular functional programming library will undoubtedly have the same issues. Especially give that (I think) the compose function shown above exists (probably in a better form) within Ramdba.

Redux

combineReducers in redux is liable to have this same problem, at least when dealing with typed reducers. I don't think the reducer's types can be inferred inside of that call. However, I'm not sure it's a common use case for Redux to have inline reducers in a combineReducers call.

Plain JavaScript

This problem would exist for any higher-order function used in this manner within JavaScript:

// given the same `compose` function from above:

const result = [1, 2, 3, 4].map(
  compose(
    x => x + x,
    x => x + '!!!',
    x => parseInt(x)
  )
);

console.log(result); // [2, 4, 6, 8]

cc/ @rkirov @IgorMinar @david-driscoll @alexeagle

Reactions are currently unavailable

Metadata

Metadata

Assignees

Labels

FixedA PR has been merged for this issueA PR has been merged for this issueIn DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions

    Morty Proxy This is a proxified and sanitized view of the page, visit original site.