Как выразить смешанную специальную и параметрическую полиморфность в машинописи? - PullRequest
0 голосов
/ 21 июня 2019

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

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

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

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

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


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

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

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

связанная проблема 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 по поводу общего значения .


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

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

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

1 Ответ

1 голос
/ 21 июня 2019

Я думаю, что лучший вариант это 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
...