I have the following types:
type State<C, S> = {
onEnter?: (context: C) => void;
onExit?: (context: C) => void;
transitions?: (context: C) => S | undefined;
};
class FSM<C, S extends Record<keyof S, State<C, keyof S>>> {
constructor(public readonly states: S, public readonly context: C) {
/* ... */
}
}
If I instantiate them like this:
const context = { foo: "bar" };
const f = new FSM(
{
a: { transitions: c => "b" }, // ERROR: c is implicitly any
b: { transitions: c => "a" } // ERROR: c is implicitly any
},
context
);
The compiler complains that c
is implicitly any
and it's unable to resolve the type of S
(it comes up as never
). Explicitly typing c
fixes the issue, eg:
a: { transitions: (c:typeof context) => "b" },
b: { transitions: (c:typeof context) => "a" }
Why is that? Shouldn't it be able to infer C
from the context
parameter of the FSM
constructor?
Wild Ass Guess : Does the order of the parameters in the FSM
constructor matter? Is it that it tries to resolve the type of states
first and does not yet know about the type of context
? If so is there a way to help the compiler "look ahead"?
NOTE : Another thing that I have a hard time understanding about this is that if I explicitly type c
with a random type - eg transitions: (c:number)=>"a";
the compiler complains that c
doesn't match the type of context. So the compiler knows what type c
is but it needs me to show it that I know it too?
Parameter order can matter to inference if there are multiple candidates, but this is not that case. What you really want here is to use contextual typing, this is where the compiler infers the type of parameters (in this case) based on the type of where the function is to be assigned.
I am not sure of the mechanics of when contextual typing gives up, but if the assigned to type requires other type parameters there is a good chance it will stop working. A simple work around is to help contextual typing out by specifying an simpler type in the type of the parameter in an intersection with the type parameter. This will ensure we still capture in S
the actual type passed in but we give contextual typing a simpler path to infer the parameter types:
type State<C, S> = {
onEnter?: (context: C) => void;
onExit?: (context: C) => void;
transitions?: (context: C) => S | undefined;
};
class FSM<C, S extends Record<keyof S, State<C, keyof S>>> {
// Record<string, State<C, keyof S>> added for contextual typing
constructor(public readonly states: S & Record<string, State<C, keyof S>>, public readonly context: C) {
/* ... */
}
}
const context = { foo: "bar" };
const f = new FSM(
{
a: { transitions: (c) => c.foo != "" ? "a" : "b" },
b: { transitions: (c) => "a" },
c: { transitions: (c) => "d" }, // Error, d is not in the state keys
},
context
);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.