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

Typescript types #83

Merged
merged 7 commits into from Sep 18, 2019
Merged

Typescript types #83

merged 7 commits into from Sep 18, 2019

Conversation

yanick
Copy link
Contributor

@yanick yanick commented Jan 23, 2019

For #81 A first stab at types, fueled by https://github.com/yanick/updeep/tree/typescript

export declare function uIf<TU, O>(predicate: FalsePredicate<O>, trueUpdates: TU, object: O): O;
export declare function uIf<TU, O>(predicate: TruePredicate<O>, trueUpdates: TU, object: O): MergedUpdate<TU, O>;
interface CurriedIf {
<TU, O>(predicate: TruePredicate<O>, trueUpdates: TU, object: O): MergedUpdate<TU, O>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's an instance where I did the (a,b) => (c) currying, but not the (a) => (b,c) or (a) => (b) => (c) ones.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bit of an explosion... is it worth doing the true/false/boolean predicates all separately? I suppose it makes it more powerful. I wonder if you could do the currying once in a parameterized type (with the predicate type and return type as parameters) and then use that 3 times.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it may be possible to Curry a function in typeland: https://gist.github.com/donnut/fd56232da58d25ceecf1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And Curry3, though the last comment in the gist link claims to solve it variadically, though I haven't tested it.

I may prefer the more explicit versions, especially since updeep has its own curry2/3/4 functions already, it matches those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://medium.com/@hernanrajchert/creating-typings-for-curry-using-ts-3-x-956da2780bbf is also a good reference for generic curried functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I played with it this morning, and re-discovered the snags I got in the first place.

So

type Curry2<F extends (a: any, b: any) => any> =
    F extends (a: infer A, b: infer B) => infer R ?
    {
        (a: A, b: B): R
        (a: A): (b: B) => R
    }
  : never;

works, but it also lose information along the way. Best thing is to go through an example. Let's take updeep's map function, and let's have

type Curry2<F extends (a: any, b: any) => any> =
    F extends (a: infer A, b: infer B) => infer R ?
    {
        (a: A, b: B): R
        (a: A): (b: B) => R
    }
  : never;

interface CurriedMap {
    <I,O extends object>(iteratee: I, object: O): Mapped<I,O>;
    <I,O extends object>(iteratee: I): (object: O)=> Mapped<I,O>;
};

const curried1 = wrapped as CurriedMap;
const curried2 = wrapped as Curry2<typeof map>;

let x = curried1({ foo:1 }, { foo: 2 });
x.foo; // x is of type Mapped<{foo: number},{foo: number}>, and this line is fine

let y = curried2({ foo:1 }, { foo: 2 });
y.foo;  // y is of type object, and this line reports an error because `foo` is not a know key of {}.

Mind you, I'm still new at Typescript and I'm not yet fully cognisant of the extents and limitations of its inference powers, but what I think happens is that Curry2 resolves Mapped<I,O> in the more generic context (so can only assume that both I and O are objects, but nothing more than that), and CurriedMap keeps the return statement as Mapped<I,O>, which only gets resolved when needed later on (and when we have more specific information on I and O).

I hope that I'm making some sense here. But TL;DR: having generic curried meta-types work, but they'll degrade the precision of the initial type.

Btw, one of my experiments was to totally replace the local curry function with lodash's, in the hope that the curried types would magically be dealt with, but I observed the same thing: the curried types would be okay, but more generic than the original types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's too bad. Yeah, I'm not sure of another way to do it either. It does seem like an issue that the type parameters can't be properly inferred. I guess doing it manually is the way to go 😖

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Fortunately up to 4 curried levels isn't that horrid to deal with, but it's still meh.

Something that makes me slightly sad is that it seems there is no good way for typescript projects to use the typescript version of their dependencies directly. For dependencies with basic types, that's okay as the .d.ts will match the signatures anyway, but for more complex signatures where inference would shine, that's sub-optimal. :-P

const inc = (i:number) => i+1;

u.map(inc, [1,2,3]); // $ExpectType number[]
u.map(inc, ["potato"]); // $ExpectType number[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be a type error, unless I'm mistaken.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think the map type could use some love. I'll have a look at it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love applied to the map signature.

export declare function uIf<TU, O>(predicate: FalsePredicate<O>, trueUpdates: TU, object: O): O;
export declare function uIf<TU, O>(predicate: TruePredicate<O>, trueUpdates: TU, object: O): MergedUpdate<TU, O>;
interface CurriedIf {
<TU, O>(predicate: TruePredicate<O>, trueUpdates: TU, object: O): MergedUpdate<TU, O>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bit of an explosion... is it worth doing the true/false/boolean predicates all separately? I suppose it makes it more powerful. I wonder if you could do the currying once in a parameterized type (with the predicate type and return type as parameters) and then use that 3 times.

export declare function uIf<TU, O>(predicate: FalsePredicate<O>, trueUpdates: TU, object: O): O;
export declare function uIf<TU, O>(predicate: TruePredicate<O>, trueUpdates: TU, object: O): MergedUpdate<TU, O>;
interface CurriedIf {
<TU, O>(predicate: TruePredicate<O>, trueUpdates: TU, object: O): MergedUpdate<TU, O>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it may be possible to Curry a function in typeland: https://gist.github.com/donnut/fd56232da58d25ceecf1

@beautyfree
Copy link

Will it be merged?

@aaronjensen
Copy link
Member

@beautyfree I think it can probably use another pass with updates to the most recent typescript, which may enable better types. I haven't had a chance to look into it, so probably not right now.

@yanick
Copy link
Contributor Author

yanick commented Sep 18, 2019

Fwiw, I would be willing to update things if I'm pointed to specifics. I already did two passes, and I suspect that if we don't nail down specific goalposts, any further pass will ends with "hmm... almost there. But... one more pass!" :-)

@aaronjensen
Copy link
Member

I appreciate that, @yanick. It looks like TS3.6 didn't help with the generic currying, so maybe we can try merging this as-is and see how it goes :)

@aaronjensen aaronjensen merged commit 1dec8b4 into substantial:master Sep 18, 2019
@aaronjensen
Copy link
Member

Released as 1.2.0, let's see how they do in the wild 😅

@yanick
Copy link
Contributor Author

yanick commented Sep 18, 2019

I appreciate that, @yanick. It looks like TS3.6 didn't help with the generic currying, so maybe we can try merging this as-is and see how it goes :)

Yay! \o/ And, btw, I realize that might have sounded more passive aggressive than intended. I just know that deployment paralysis is a thing, and the best way to combat it is to define specifically what needs to be done for the thing to be deemed worthy and deployable. :-)

In any case, I'm glad it's out. And while I'm sure the types will need tweaking, at least now we can see how they actually fare against the real world. :-)

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

Successfully merging this pull request may close these issues.

None yet

3 participants