Reputation: 9332
My Types:
interface Pair<FirstValue, SecondValue> {
first: () => FirstValue,
second: () => SecondValue,
toString: () => string
}
type StateRunner = <V, S>(state: S) => Pair<V, S>;
My Code:
const Pair = <V1, V2>(v1, v2): Pair<V1, V2> => ({
first: () => v1,
second: () => v2,
toString: () => `Pair(${v1}, ${v2})`
});
const myRunner: StateRunner = (state) => Pair("hello", state);
const result = myRunner(someString, someData);
In VSCode, I get this:
I'm 100% positive that I'm just not understanding Generics very well (I'm new to TypeScript), but I would think that it would list the result as:
Pair<string, {
size: string,
active: boolean
}>
What am I missing? Am I expecting too much from TypeScript/VSCode? The main reason I'm confused is that, when I use Pair
directly, intellisense is totally aware of what the return types of first
and second
will be.
Upvotes: 0
Views: 85
Reputation: 329838
Aside: I find myself having to alter your example code just to get to the place where your question exists. Examples: annotate v1
and v2
:
const Pair = <V1, V2>(v1: V1, v2: V2): Pair<V1, V2> => ({
first: () => v1,
second: () => v2,
toString: () => `Pair(${v1}, ${v2})`
});
and myRunner
takes just one argument:
const result = myRunner({ a: "" });
I assume those are what you meant.
The problem with your code is that a function of type StateRunner
claims to be able to take a value of generic type S
chosen by the caller, and produce a Pair<V, S>
for a generic type V
also chosen by the caller. That's not possible, is it? Since no value of type V
is passed in, there's no plausible way by which the output will be a Pair<V, S>
. One symptom of this is that when you let the compiler infer the types that the caller would specify, it has absolutely no idea what V
will be. So it infers unknown
.
Looking at your implementation, you want S
to be chosen by the caller and V
to be chosen by the implementer. That means V
and S
should not be generics of the same kind. I'd expect StateRunner
to itself be generic, like this:
type StateRunner<V> = <S>(state: S) => Pair<V, S>;
So V
is part of the StateRunner
type, chosen by the implementer, and S
is part of the generic function type, chosen by the caller. And you won't have a value of type StateRunner
; you'll have a value of type StateRunner<V>
for a particular V
. In the case of myRunner
, it will be a StateRunner<string>
:
const myRunnerAnnotated: StateRunner<string> = state => Pair("hello", state);
Now things work:
const r = myRunnerAnnotated({ a: "" }); // Pair<string, {a: string}>
If you don't want to manually annotate something as StateRunner<string>
you can use a helper function to infer V
for you:
const asStateRunner = <V>(s: StateRunner<V>) => s;
And use it like this:
const myRunner = asStateRunner((state) => Pair("hello", state));
// const myRunner: StateRunner<string>
You can see that myRunner
is inferred to be a StateRunner<string>
, and then result
works the way you want as before:
const result = myRunner({ a: "" }); // Pair<string, {a: string}>
Okay, hope that helps give you some direction. Good luck!
Upvotes: 2