如何在打字稿中表达混合的即席和参数多态?

时间:2019-06-21 05:10:57

标签: typescript generics overloading parametric-polymorphism adhoc-polymorphism

我不确定是否要在标题中描述当前的问题。 我要问的是来自以下要求。

我正在尝试为有限状态机的状态做一个抽象,并提出以下定义(在打字稿中)

interface IState {
    send<T, E>(message: T, callback?:(event: E)=>void): IState;
}

我要表达的是,有限状态机的状态应该能够接受消息并返回新状态,并带有可选的回调以在过渡期间处理事件。

将此接口实现为具体状态时,会出现问题。


例如,我正在尝试制作一个只有两个状态的简单状态机,,并带有三个可能的消息,左转右转。下表显示了它们之间的关系。

enter image description here

关键是我要限制状态向左,只接受继续右转消息,而发送向左转左移是一个编译错误

我试图在打字稿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();
  }
}

该实现没有编译错误,并且自动完成可以按预期工作。但由于它看起来很怪异,所以我问了一个问题TypeScript function generic can only work for function overload with more than one signatures

感谢该问题下的善意答复,我知道将重载函数分配给泛型函数是错误的。但是,如何在保持特定状态仅接受所需类型的消息的同时,表达状态的一般接口?

related GitHub issue


我可以提出的另一种抽象是

interface IState<T, E, R extends IState<?, ?, ?>> {
    send(message: T, callback?:(event: E)=>void): R;
}

但是返回类型是递归的,我不知道该为上面的三个问号填充什么。

一个简单的版本可能是

interface IState<T, E> {
    send(message: T, callback?:(event: E)=>void): IState<any, any>;
}

除了返回类型中令人讨厌的 any 之外,它的行为似乎与其他类似。

interface IState {
    send<T, E>(message: T, callback?:(event: E)=>void): IState;
}

我找到了maybe related issue in GitHub about generic value


这个问题定义明确吗?

如果为true,则上述方法列表中是否有正确的解决方案?

如果为假,正确的解决方案是什么?

1 个答案:

答案 0 :(得分:1)

我认为最好的选择就是这个interface IState<T, E, R extends IState<?, ?, ?>>。可以用any代替问号,我们并不真正在乎后面的状态是什么。

interface IState<T, E, R extends IState<any, any, any>> {
    send(message: T, callback?: (event: E) => void): R;
}

class Left implements IState<'go-on', never, Left>, IState<'turn-right', never, Right>{
    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<'go-on', never, Right>, IState<'turn-left', never, Left> {
    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();
    }
}

let left = new Left();
let left_go_on: Left = left.send("go-on")
let left_turn_right: Right = left.send("turn-right")
left.send("turn-left") // error


let right = new Right();
let right_go_on: Right = right.send("go-on")
let right_turn_right: Left = right.send("turn-left")
right.send("turn-right") // error

或者,如果您只想在implements子句中使用接口,这也可以:

interface IState<T extends [any, any, IState<[any, any, any]>]> {
    send: T extends T  ? ((message: T[0], callback?: (event: T[1]) => void) => T[2]) : never
}

class Left implements IState<['go-on', never, Left] | ['turn-right', never, Right]>{
    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<['go-on', never, Right] | ['turn-left', never, Left]> {
    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();
    }
}

let left = new Left();
let left_go_on: Left = left.send("go-on")
let left_turn_right: Right = left.send("turn-right")
left.send("turn-left") // error


let right = new Right();
let right_go_on: Right = right.send("go-on")
let right_turn_right: Left = right.send("turn-left")
right.send("turn-right") // error