sindresorhus / type-fest

A collection of essential TypeScript types
Creative Commons Zero v1.0 Universal
14k stars 531 forks source link

Proposal: Partial/Complete (curry) #39

Open joeflateau opened 5 years ago

joeflateau commented 5 years ago

This may be a limitation in the type system, but thought I'd drop this proposal here. The idea here is that there should be some way of expressing that P is an object with keys that are a subset of the keys of T, then those keys that are missing on P from T are required to be supplied as U.

interface IComplete {
    a: string;
    b: string
}

function partial<T, P extends Partial<T>>(defaults: P) {
    return function<U extends MissingFrom<T, P>>(remaining: U) {
        const complete: T = { ...defaults, ...remaining };
        return complete;
    }
}

const curried = partial({a:"foo"});
const bar: IComplete = curried({b:"bar"});
const baz: IComplete = curried({b:"baz"});

Upvote & Fund

Fund with Polar

joeflateau commented 5 years ago

The built-in Partial doesn't work here as it does retain the keys of T but as "undefinable"

WORMSS commented 5 years ago

Sorry, I am not super great with Types yet, so I am having trouble understanding this, but..

How does the function partial/curried know that {a:"foo"} should be a accepting IComplete? Is it because bar and baz are of IComplete, so it infers backwards that curried must accept IComplete too? (Just not used to that way of thinking yet).

Also, Shouldn't curried also be able to accept properties that were passed into partial since they are only "defaults" ?? Because to me it would read that a would have to be omiitted?

So you couldn't do

const bar: IComplete = curried({b:"bar"});
const baz: IComplete = curried({a: "not foo", b:"baz"});

Or am I reading this wrong?

resynth1943 commented 4 years ago

@joeflateau Nice little riddle. I've solved it.

interface Complete {
    a: number;
    b: number;
}

function curried<PartialOne extends Partial<Complete>>(partialOne: PartialOne) {
    return function <PartialTwo extends Omit<Complete, keyof PartialOne>>(partialTwo: PartialTwo) {
        return { ...partialOne, ...partialTwo };
    }
}

const complete = curried({
    a: 2
})({
    b: 2
});

const completeCast: Complete = complete;

The trick is to not type the return type of the returned function from curried. It doesn't matter anyway, because as you can see, it's assignable to Complete.

Feel free to test this one yourself..

resynth1943 commented 4 years ago

Also, sorry for what's basically a bump, but I'm trying to hack away at the long-standing issues here. Hope I helped you anyway, despite the huge time gap. 😛

joeflateau commented 4 years ago

@resynth1943 haha, no worries. I'm going to need a bit to wrap my head around your solution, but that's awesome

resynth1943 commented 4 years ago

No problem. ♥️ By the way, my solution stops you from passing the same key in both objects, and it's all typed correctly. It should be foolproof. 😛

joeflateau commented 4 years ago

@resynth1943 where do we go from here? want me to PR?

joeflateau commented 4 years ago

wrapping in a function to let a type be passed into the curry. still haven't figured out how to make the result of completed() return type T

interface Complete {
  a: number;
  b: number;
}

function curry<T>() {
  return function curried<Curried extends Partial<T>>(curried: Curried) {
    return function completed<Completed extends Omit<T, keyof Curried>>(
      completed: Completed
    ) {
      return { ...curried, ...completed };
    };
  };
}

const complete: Complete = curry<Complete>()({
  a: 1
})({
  b: 2
});
joeflateau commented 4 years ago

Could cast { ...curried, ...completed } as unknown as T but that feels wrong

WORMSS commented 4 years ago

Is this even possible? Because you couldn't pass return type of curried around. It would have to know the solidified value of curried argument to know what it is? For example

const v = JSON.parse(API.get());
const a: ??? = curried<Completed>(v);
const c: Completed = a(somethingElse);

What would you write into a:???