如何在打字稿中表达混合的即席和参数多态性? [英] How to express mixed ad-hoc and parametric polymorphic in typescript?

查看:113
本文介绍了如何在打字稿中表达混合的即席和参数多态性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

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


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

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

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

该实现没有编译错误,并且自动完成可以按预期工作.但由于它看起来很奇怪,所以我问了一个问题相关的GitHub问题


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

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;
}

我已经找到了在GitHub中与通用值有关的问题./p>


这个问题定义明确吗?

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

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

解决方案

我认为最好的选择就是这个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子句中使用on接口,这也可以:

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

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?

related GitHub issue


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 with any we don't really care what the state after is only that it is some state.

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

Or if you want to only have on interface in the implements clause this also works:

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

这篇关于如何在打字稿中表达混合的即席和参数多态性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆