typed-typings / npm-ramda

TypeScript's type definitions for Ramda
MIT License
384 stars 64 forks source link

example pipe #365

Open lstkz opened 6 years ago

lstkz commented 6 years ago

Is it expected that we must provide all generic types?

this works

var isEven = (n: number) => n % 2 === 0;
const result = R.pipe<number[], number[], number[], number>(
            R.filter(isEven),
            R.map((n) => n * n),
            items => items[0],
          )([1, 2, 3, 4]);

but this, reports an error

var isEven = (n: number) => n % 2 === 0;
const result = R.pipe(
            R.filter(isEven),
            R.map((n) => n * n),
            items => items[0],
          )([1, 2, 3, 4]);

image

I tried to check tests https://github.com/types/npm-ramda/blob/master/tests/pipe.ts but all examples are without input data.

ikatyang commented 6 years ago

Unfortunately, TS currently cannot handle higher-order function properly, we have to provide more information to help the inference:

const result = R.pipe<number[], number[], number[], number>(
  R.filter(isEven),
  R.map(n => n * n),
  items => items[0],
)
const result = R.pipe(
  // there're 3 possible types (List, Object, Filterable)
  // defaults to Mixed (List | Object | Filterable) <-- that's why option1 works
  R.filter(isEven)<'1', 'list'>(),

  // contextual type inference is not working here
  // since we use 0-param func to select overload.
  R.map((n: number) => n * n)<'1', 'list'>(),

  items => items[0],
)
lstkz commented 6 years ago

After using Ramda + TS for 2 months, there are still many confusing use cases. Selectable overloads affect readability and they are not obvious to write.

Consider option 2 from above example.

It can be written as

R.pipe(
  R.filter(isEven)<'1', 'list'>(),
  // much simpler, no need for argument annotation, but no curring
  items => items.map(n => n * n),
);

Or we can add helper methods in Ramda. I think I will be starting writing like this:

// extension for ramda
export const mapList = <T1, TR>(fn: (a: T1) => TR) => (items: T1[]): TR[] =>
  R.map(fn, items);

export const filterList = <T1>(fn: (a: T1) => boolean) => (items: T1[]): T1[] =>
  R.filter(fn, items);

// and then new version
R.pipe(
  R.filterList(isEven),
  R.mapList(n => n * n),
);

I am wondering, how people use Ramda +TS in their real projects? Or there are other alternatives for TS?

KiaraGrouwstra commented 6 years ago

@lsentkiewicz: I agree it's a major pain point. I'm barely doing front-end anymore so can't really attest to usage well. I've hoped to resolve some of these issues by improving type inference in TS, but that definitely isn't where the short-term gains lie, especially given this isn't quite first priority to the TS team.

There does seem to be a bit of a split between TS definitions for JS FP libs (e.g. here) vs. TS-first libraries, such as those by gcanti. One might conclude that for typing purposes it'd help to use a library designed with types in mind, with structure-specific function variants such as the mapList you mentioned.

That probably went against the grain of the ADT/Haskell philosophy Ramda intended to port to JS though, so it's a fair question what options would be out there to achieve this. Suggestions welcome.

I think Sanctuary for one tried to avoid Ramda's generic bits, though most TS FP seems to focus on ADTs. Lodash-FP is probably an option as well, though I don't know to what extent it has been typed -- last I checked Lodash typings did not seem an improvement over this repo.

If you do conclude any of the alternatives ends up superior, feel free to report back your experience -- I'd be interested whether it's actionable for improvement here or an alternative to Ramda superior for use with TS.

Maybe we'd first wanna check which parts that Ramda does we would or would not want here. What I can think of:

good:

bad:

Now, I'd made an attempt to emulate the currying part at pointfree, though typing it in any meaningful way isn't really doable yet in any generic kind of way, bar manually retyping the standard library / doing some AST transforms on TS's lib.d.ts.

lstkz commented 6 years ago

I decided to create my own utility library https://github.com/remeda/remeda. It's not pure FP like Ramda, but no manual annotations are required, and it's not painful. Works pretty well during development :)