Объявление типов s-выражений в TypeScript - PullRequest
0 голосов
/ 26 августа 2018

Клиент TypeScript связывается с сервером, используя s-выражения, закодированные в формате JSON. Для простоты предположим, что алфавит выражений состоит из одной функции-терминатора 'rx', которая принимает строку в качестве параметра, и функций 'и', 'или', которые принимают другие выражения в качестве параметров. Например:

const msg: Expr = ['and', ['rx', 'a'],
                          ['or', ['rx', 'b'],
                                 ['rx', 'c']]];

Простой подход к определению типа Expr не работает из-за недопустимой рекурсии:

type ExprRx = ['rx', string];
type ExprAnd = ['and', ...Expr[]];
type ExprOr = ['or', ...Expr[]];

type Expr = ExprRx | ExprAnd | ExprOr;

Как может выглядеть такой тип? Можно ли даже объявить это в современной машинописи?

1 Ответ

0 голосов
/ 26 августа 2018

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

interface ExprRx { op: 'rx', value: string };
interface ExprAnd { op: 'and', operands: Expr[] };
interface ExprOr { op: 'or', operands: Expr[] };

type Expr = ExprAnd | ExprOr | ExprRx;


const msg: Expr = {
    op: 'and',
    operands: [{
        op: 'rx',
        value: 'a'
    }, {
        op: 'or',
        operands: [
            { op: 'rx', value: 'b' },
            { op: 'rx', value: 'c' }
        ]
    }]
};

function evaluate(msg: Expr, map: { [n: string]: boolean}) : boolean {
    // type guards 
    switch (msg.op) {
        case 'rx': return map[msg.value];
        case 'and' : return msg.operands.every(m => evaluate(m, map))
        case 'or' : return msg.operands.some(m => evaluate(m, map))
        default : throw "error";
    }
}
evaluate(msg, {
    'a' : true,
    'b': false,
    'c': false
});

Теперь эта версия немного более многословна, чем ваша версия кортежа.Можем ли мы сделать то же самое с помощью просто кортежей?Ну, это немного сложнее.У нас может быть интерфейс, расширяющий тип кортежа и использующий его, но это имеет несколько проблем.Во-первых, мы не можем расширять тип кортежа напрямую, нам понадобится дополнительный псевдоним типа.Во-вторых, мы не можем использовать rest в кортежах (не знаю, почему просто ограничение complier, я получаю сообщение об ошибке, что неправильно расширяю кортеж, содержащий выражение rest).В-третьих, средства защиты типов не будут работать с типами кортежей, например, в функции evaluate, которую я написал выше:

type Tuple<T0, T1> = [T0, T1]
type TupleRepeat<T0, T1> = [T0, T1, T1]
interface ExprRx extends  Tuple<'rx', string> {};
interface ExprAnd extends TupleRepeat< 'and', Expr> {};
interface ExprOr extends TupleRepeat <'or', Expr> {};

type Expr = ExprRx | ExprAnd | ExprOr

const msg: Expr = ['and', ['rx', 'a'],
                        ['or', ['rx', 'b'],
                                ['rx', 'c']]];


function evaluate(msg: Expr, map: { [n: string]: boolean}) : boolean {
    // no type guards, we need extra assertions.
    switch (msg[0]) {
        case 'rx': return map[msg[1] as string];
        case 'and' : return (msg.slice(1) as Expr[]).every(m => evaluate(m, map))
        case 'or' : return (msg.slice(1) as Expr[]).some(m => evaluate(m, map))
        default : throw "error";
    }
}
evaluate(msg, {
    'a' : true,
    'b': false,
    'c': false
});
...