Как выразить смешанную специальную и параметрическую полиморфность в машинописи?

Я не уверен, что я сейчас описываю квесты в заголовке. То, что я пытаюсь спросить, вытекает из следующего требования.

Я пытаюсь сделать реферат для состояний конечных автоматов и приходит со следующим определением (в машинописи)

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

Я пытаюсь выразить, что состояние конечного автомата должно быть в состоянии принимать сообщения и возвращать новое состояние с необязательным обратным вызовом для обработки событий s во время перехода.

При реализации этого интерфейса в конкретных состояниях, есть проблема.


Например, я пытаюсь создать простой конечный автомат только с двумя состояниями, ВЛЕВО и ВПРАВО, с тремя возможными сообщениями: включение, поворот налево, поворот направо. Следующая таблица показывает их отношения.

Ключевым моментом является то, что я хочу ограничить состояние LEFT, только принимать сообщения о включении и повороте вправо, в то время как отправка поворота влево в LEFT, как ожидается, будет ошибкой компиляции.

Я попытался реализовать как показано в машинописи 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 может работать только при перегрузке функции более чем с одной подписью.

Спасибо за добрые ответы на этот вопрос, я понимаю, что присваивать функции перегрузки универсальным функциям неправильно. Но тогда как я могу выразить общий интерфейс состояния, сохраняя при этом определенное состояние, принимая только нужные типы сообщений?

связанная проблема 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>;
}

Кажется, он ведет себя как-то, кроме раздражающего любого в типе возврата.

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

Я нашел, возможно, связанную проблему в GitHub по поводу общей стоимости.


Этот вопрос хорошо определен?

Если это правда, есть ли правильное решение в списке методов выше?

Если неверно, какое правильное решение?

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
Другие вопросы по тегам