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

Typing for pipe and compose #29

Closed
ccapndave opened this issue Sep 24, 2015 · 15 comments
Closed

Typing for pipe and compose #29

ccapndave opened this issue Sep 24, 2015 · 15 comments

Comments

@ccapndave
Copy link
Contributor

pipe/compose are really the main building blocks of Ramda, and it seems that a lot of the time Typescript doesn't correctly figure out the types from what's in the chain and its necessary to explicitly put the type into the generic. Sometimes even this doesn't work and you have to <any> the whole pipeline to get it to compile. This isn't a specific issue as such, and I don't have any particular solutions but I just wanted to put it out there:

What could we do to improve the typing of pipe/compose?

@donnut
Copy link
Collaborator

donnut commented Sep 24, 2015

Yes, compose/pipe are nasty functions to type using the TS capabilities. Can you share some examples you ran into that require explicity typing?

Did you try the latest (and current) commit of typescript-ramda? I've tried to improve the ability of compose to handle functions with a arity>1.

@ccapndave
Copy link
Contributor Author

Yup, I'm on the latest release. I hit against typing errors on pipe all the time (I actually rarely use compose), so I can definitely keep a log of example in this ticket. Here is one from today:

export const capitalize = (str: string): string => {
    return R.pipe(
        R.split(''),
        R.adjust(R.toUpper, 0),
        R.join('')
    )(str); // Error TS2345: Argument of type 'string' is not assignable to parameter of type 'any[]'.
}

FYI, this does type correctly if I reverse the order and use compose instead!

@donnut
Copy link
Collaborator

donnut commented Sep 25, 2015

Hmm, I see that pipe is not yet on the same level as compose, so that explains probable that your example works with compose but no with pipe. I'll try to rewrite pipe tonight.

@ccapndave
Copy link
Contributor Author

That would be awesome. Thanks!

@donnut
Copy link
Collaborator

donnut commented Sep 25, 2015

Done, let me know if that doesn't work for you.

@donnut donnut closed this as completed Sep 25, 2015
@ccapndave
Copy link
Contributor Author

Thanks for this. A bunch of problems have been fixed, but a bunch of new problems have popped up. Here is a strange one:

const letters = R.pipe(R.append("a"), R.uniq)(["a", "b", "c"]);

letters should be typed string[], but seems to be typed to {}[] instead.

@ccapndave
Copy link
Contributor Author

It actually turns out that this can be fixed by applying generics to append: const letters = R.pipe(R.append<string, string>("a"), R.uniq)(["a", "b", "c"]); so I guess its not a problem with pipe

@ccapndave
Copy link
Contributor Author

See #31

@ccapndave
Copy link
Contributor Author

Here is another example of a pipeline which type-checks strangely:

R.pipe(
    R.split(''),
    R.map(letter => ([ letter ]))
)("dave");

Somehow the string gets lost along the pipeline, and the return type of this expression is {}[][] instead of string[][].

@ccapndave
Copy link
Contributor Author

And another where the return type of b is {}

const b = R.pipe(
    R.prop<string>('name'),
    R.length
)({ name: 'dave' });

It seems like Typescript somehow loses the type of the previous link in the chain.

@donnut
Copy link
Collaborator

donnut commented Sep 28, 2015

It seems that map can not determine the input type, so it chooses any ({}).

map(x => x[]) :: any[] => any[]

This is were Typescript looses its type. This is due to the following
definition of map

map<T, U>(fn: (x: T) => U): (list: T[]) => U[];

Because T and U in (x: T) => U are not defined in the function definition, both are set to any.
It doesn't use type info of its return type (list: string[]). I do not see any solution for this. I do wonder if Flow does a better job here. They advertise with a better type inference than Typescript.

The other example:

const b = R.pipe(
    R.prop<string>('name'),
    R.length
)({ name: 'dave' });

works well if you remove the , but only because length as a fixed return type of number.

@donnut donnut reopened this Sep 28, 2015
@wesleycho
Copy link

FWIW, I ran into this issue as well - this is where I ran into problems

let mapPackages = R.partial(R.map, [test => test.package]);
let filterBuild = R.partial(R.filter, [test => test.build === build]);
let getPackages = R.compose(R.uniq, mapPackages, filterBuild);
this.packages = getPackages(this._tests);

TypeScript didn't like my mapPackages function. Granted, my _tests and test key aren't typed, but it's a bit unfortunate there's some trouble here.

@MikeyBurkman
Copy link

@wesleycho If you leave out the R.partials and just use the standard partial application of R.map and R.filter, it works almost perfectly. I added a quick type interface for build/package.

interface Foo {
  build: string,
  package: string
}

const build = 'dev';
let mapPackages = R.map((test: Foo) => test.package);
let filterBuild = R.filter((test: Foo) => test.build === build);
let getPackages = R.compose(R.uniq, mapPackages, filterBuild);

let foos = [{
  build: 'dev',
  package: 'devPackage'
}, {
  build: 'prod',
  package: 'prodPackage'
}, {
  build: 'dev',
  package: 'devPackage'
}];

let foosFiltered = getPackages(foos);
console.log('foosFiltered: ' , foosFiltered);

The only catch is that it loses the return type with the R.uniq in there. Unfortunately, I don't know how to get a generic function reference in TS. (R.uniq<string> won't compile.) But worst case scenario, you can create a tiny function wrapper that maintains the type:

let getPackages = R.compose((packages: string[]) => R.uniq(packages), mapPackages, filterBuild);

@donnut
Copy link
Collaborator

donnut commented May 30, 2016

See microsoft/TypeScript#5254

{} is the type that appears when there are no inference sources during generic type inference. We don't do any inference from call sites, ...

@KiaraGrouwstra
Copy link
Member

This appears to trigger whenever you are composing functions with generics; the <T>(T) => T or the like will be resolved by TS too soon and degenerate to ({}) => {} or the like before it gets to consider the types of its input, see #86, #92 and #101.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants