Существует ограничение рекурсивности псевдонимов типов, они не могут (за исключением очень ограниченных обстоятельств быть рекурсивными).К счастью, интерфейсы могут быть рекурсивными.Реализация интерфейса может выглядеть примерно так:
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
});