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

Generics lost on currying #78

Closed
ivan-kleshnin opened this issue Aug 3, 2016 · 8 comments
Closed

Generics lost on currying #78

ivan-kleshnin opened this issue Aug 3, 2016 · 8 comments
Assignees

Comments

@ivan-kleshnin
Copy link

ivan-kleshnin commented Aug 3, 2016

Also see: #73

This (correct) function does not compile

import * as R from "ramda"
import {curry} from "ramda"

let updateBy = curry((pred, val, array) => {
  let i = R.findIndex(pred, array);
  if (i >= 0) {
    return R.update(i, val, array);
  } else {
    return array;
  }
})
Argument of type '{}' is not assignable to parameter of type '(a: {}) => boolean'.
  Type '{}' provides no match for the signature '(a: {}): boolean' (2345)
    at getOutput (/usr/local/lib/node_modules/ts-node/src/index.ts:280:15)
    at compile (/usr/local/lib/node_modules/ts-node/src/index.ts:289:14)
    at loader (/usr/local/lib/node_modules/ts-node/src/index.ts:304:23)
    at Object.require.extensions.(anonymous function) [as .ts] (/usr/local/lib/node_modules/ts-node/src/index.ts:321:14)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Function.Module.runMain (module.js:575:10)
    at Object.<anonymous> (/usr/local/lib/node_modules/ts-node/src/_bin.ts:172:12)
    at Module._compile (module.js:541:32)
@KiaraGrouwstra KiaraGrouwstra changed the title Another currying problem Curry loses generics Dec 10, 2016
@KiaraGrouwstra
Copy link
Member

Thank you for bringing this up. By now, your function will in fact compile, which I hope addresses the original scope of this issue.
However, I think you've helped uncover a related issue, as your function, while it compiles, does not get typed correctly as the original (uncurried) version might have.
I've tried this as follows:

let updateBy = R.curry(<T>(pred: R.Pred<T>, val: T, array: T[]): T[] => {
    let i = R.findIndex(pred, array);
    if (i >= 0) {
        return R.update(i, val, array);
    } else {
        return array;
    }
});
let res: number[] = updateBy((n: number) => n > 1, 0, [1,2,3]);

TypeScript currently feels the result should be {}[] rather than number[]. Inspecting the lambda function and its curried wrapper reveals what appears to be the issue: the original had its generic, while the curried version does not.

@KiaraGrouwstra KiaraGrouwstra changed the title Curry loses generics Generics lost on currying/composition Dec 10, 2016
@KiaraGrouwstra KiaraGrouwstra changed the title Generics lost on currying/composition Generics lost on currying Dec 11, 2016
@KiaraGrouwstra
Copy link
Member

PoC at microsoft/TypeScript#5453 (comment).

@KiaraGrouwstra
Copy link
Member

Once we're able to implement curry the above way, hopefully it could be adjusted to account for placeholders as well (CC @ikatyang).

The way I'm imagining this is a three-param function F getting called would start with a type-level parameter index stack of [0, 1, 2]. Given F("foo", R.__), it'd go over the passed params one by one.
It'd note "foo" is a real (non-placeholder) param, making for known params { 0: "foo" }, popping 0 off the argument indices, leaving unfilled arguments [1, 2].
At this point it'd note the second param passed is a placeholder, and move its index to the end, yielding a function with upcoming parameter order [2, 1].

Implementation todo.

@millsp
Copy link

millsp commented Mar 8, 2019

@tycho01 @ivan-kleshnin

I just added support for R.__ and curry. It was built thanks to recursive types. I released an article on Medium about this, if you want to learn how to create your own. The PR was merged into DefinitelyTyped just now and is available at "@types/ramda": "^0.26.1".

In fact, we don't need to wait for variadic kinds. Even with variadic kinds, we would still need recursive types to build analysis types. But it would make the development much easier though.

https://github.com/pirix-gh/medium/blob/master/types-curry-ramda/src/index.ts

@KiaraGrouwstra
Copy link
Member

@pirix-gh: Thanks! I enjoyed the Medium post (link for others).

As you may have noticed, some time has passed since I tried type stuff, meaning a lot of my methods will now seem outdated -- we lacked conditional types / infer / Concat, hence I used stuff like the numerical objects ({ 0: blah }). I agree variadic kinds no longer matter since Concat.

I agree the recursive types issue is pretty important, and I hope they'll acknowledge its importance -- losing recursion would essentially kill any advanced types.

Regressions like that were a massive issue for my type repo, particularly as a way to unit test against type non-termination did not exist, meaning things would randomly break on TS upgrades, and it was very hard for me to pin-point which parts broke when and why.

I think you've done a great job by getting your types into DT already, as they will do regression tests against this on PRs. A next step up could be to even get some recursive types into the actual TS unit tests, as this could help raise red flags as soon as a TS dev would try something impacting recursion behavior. Then again, just DT may suffice already.

Is there any particular meaning to analysis types though? I may have been out of the loop for too long.

On another note, I think ahejlsberg's recent PR and @pirix-gh's curry implementation pretty much address the original scope of this particular issue! Let's mark it as fixed. :)

@millsp
Copy link

millsp commented Mar 9, 2019

@tycho01 Thank you! I kind of enjoyed myself writing this :)

What I mean by "analysis" types is just types that are "smarter". I like to treat types as if they were functions. As a result, they can be combined together to do input transformation/analysis to output (like we would do in any programming language).

And yes, I'm also happy that the types were accepted. It doesn't seem that they use recursive types there at TS, but now there's no regression possible 🗡️. While I was building these recursive types, TS introduced a new error, so I could feel the urge to publish them. These issues arise because TS tries to compute types that receive parameters that haven't been received yet. Like for example:

type Concat<T1 extends any[], T2 extends any[]> =
    Reverse<Cast<Reverse<T1>, any[]>, T2>

Complains that "Type instantiation is excessively deep and possibly infinite". But in fact it just assumed that it's infinite (because of any[]). It should rather wait to receive T1 and T2 before screaming at us... Bit irritating. It could wait for the parameters to be provided and only then compute Concat:

Test00<[any, number], []> // no error
Test00<[...any[]], []> // error, infinite

I described a valid workaround for this at the top of the repository... for now. But we won't want this workaround if recursive types become more mainstream, it's kind of ugly and unnecessary.
So I started formulating a request here microsoft/TypeScript#30188
After a long talk with Nathan about this, I will propose the team to stop computing types that are waiting for parameters (on this same issue soon).

@KiaraGrouwstra
Copy link
Member

@pirix-gh:
Sounds good, if they take it we're good!

On the eager depth check, is the workaround that Tail or HasTail, and what's the issue with the workaround for end-users (who presumably won't have to see the internals to use curry)?
(If necessary, other workarounds might be possible like going through ReadonlyArray types like { 0: foo, length: 1 }.)

On another note, once Ramda types can be expressed as such analysis types, especially wrapped in a Curry<> like yours, I imagine the demand for a codegen-based approach as currently used by the present repo (based on the awesome work by @ikatyang!) will likely diminish. That was kind of my goal, though things hadn't really progressed enough for it to be within reach at the time.

@millsp
Copy link

millsp commented Mar 9, 2019

Thanks @tycho01 for the tip. It breaks on some of the types though. I am not sure I understand how it works exactly.
And codegen is a good way to approach. Like you said, most of the work is done 👍 .
So I can also say thanks a lot to the maturing of TypeScript, it was impossible back then without the latest features.

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

No branches or pull requests

3 participants