I'm not sure if I'm describing the questing currently in the title. What I'm trying to ask comes from the following requirement.
I'm trying to make an abstract for states of finite state machines and comes up with the following definition (in typescript)
interface IState {
send<T, E>(message: T, callback?:(event: E)=>void): IState;
}
I'm trying to express that a state of the finite state machine should be able to accept messages and return new state, with an optional callback to handle event s during the transition.
When implementing this interface into concrete states, There is a problem.
For example, I'm trying to make a simple state machine with only two states, LEFT, and RIGHT, with three possible messages go-on, turn-left, turn-right. The following table shows their relations.
The key point is that I want to constrain state LEFT only accept go-on and turn-right messages, while send turn-left to LEFT is expected to be a compile error.
I've tried to implement like the following in typescript 3.4.5.
class Left implements IState {
send(m: 'go-on', cb?: (e: never) => void): Left;
send(m: 'turn-right', cb?: (e: never) => void): Right;
send(m: 'go-on' | 'turn-right', cb?: any) {
return m === 'go-on' ? new Left() : new Right();
}
}
class Right implements IState {
send(m: 'go-on', cb?: (e: never) => void): Right;
send(m: 'turn-left', cb?: (e: never) => void): Left;
send(m: 'go-on' | 'turn-left', cb?: any) {
return m === 'go-on' ? new Right() : new Left();
}
}
The implementation does not have compile error and auto-complete do work as expected. But since it looks weird, I've asked a question TypeScript function generic can only work for function overload with more than one signatures.
Thanks for the kind replies under that question, I understand assign overloading functions to generic functions is wrong. But then how can I express the general interface of state while keeping specific state accept only desired types of messages?
Another abstraction I can come up with is
interface IState<T, E, R extends IState<?, ?, ?>> {
send(message: T, callback?:(event: E)=>void): R;
}
But the return type is recursive and I don't know what to fill for those three questing marks above.
A simpler version could be
interface IState<T, E> {
send(message: T, callback?:(event: E)=>void): IState<any, any>;
}
It seems to behave like except the annoying any in the return type.
interface IState {
send<T, E>(message: T, callback?:(event: E)=>void): IState;
}
I've found an maybe related issue in GitHub about generic value.
Is this question well-defined?
If true, is there a correct solution in those methods list above?
If false, what's the correct solution?
I think the best option is this one
interface IState<T, E, R extends IState<?, ?, ?>>
. The question marks can be replaced withany
we don't really care what the state after is only that it is some state.Or if you want to only have on interface in the
implements
clause this also works: