Ορίστε έναν αναδρομικό πίνακα άρτιου μήκους σε TypeScript
Προσπαθώ να γράψω μια δήλωση τύπου TypeScript για ένα υποσύνολο των εκφράσεων στυλ του Mapbox, μια γλώσσα τύπου Lisp που βασίζεται σε JSON. Για παράδειγμα:
["+", 1, ["*", 2, 2], 3]
= 8
Ένας από τους τελεστές είναι "case", ο οποίος σας επιτρέπει να γράψετε συνθήκες:
["case", true, "yes", "no"]
= "yes"
Αυτό παίρνει ζεύγη boolean και τιμών, καθώς και μια προεπιλεγμένη τιμή (δείτε τα έγγραφα).
Υπάρχει κάποιος τρόπος να μοντελοποιηθεί «ένα πίνακας άρτιου μήκους» σε TypeScript, ιδιαίτερα ένας των οποίων τα στοιχεία μπορεί να αναφέρονται αναδρομικά στον εαυτό τους; Να μια προσπάθεια:
type Expression = number | string | boolean | CallExpression;
type CallExpression = MathCall | CaseCall;
type MathCall = [
'+' | '-' | '/' | '*' | '>' | '<',
Expression,
Expression,
];
type CaseCallBase = [
'case',
Expression,
...CaseCallParams,
];
type CaseCallParams = [
// ~~~~~~~~~~~~~~ Type alias 'CaseCallParams' circularly references itself. (2456)
Expression,
Expression,
...([] | CaseCallParams)
];
Γνωρίζω ότι υπάρχουν τρόποι να ορίσει κανείς ένα tuple μήκους N στο TypeScript, αλλά δεν κατάφερα να αποφύγω το σφάλμα κυκλικής αναφοράς με αυτά.
Αυτό είναι το πιο κοντινό που έχω φτάσει με ένα interface, αν και πρέπει να γράψεις όλα τα πεδία και να τα περιορίσεις σε ένα συγκεκριμένο μήκος. Ο ορισμός ενός πίνακα με ένα interface φαίνεται αδέξιος και οδηγεί σε συγχυτικά σφάλματα, αλλά δεν έχω βρει καμία εναλλακτική.
type Expression = number | string | boolean | CallExpression;
type CallExpression = MathCall | CaseCall;
type MathCall = [
'+' | '-' | '/' | '*' | '>' | '<',
Expression,
Expression,
];
interface CaseCall {
0: 'case';
1: Expression;
2: Expression;
3: Expression;
4?: Expression;
5?: Expression;
6?: Expression;
7?: Expression;
// etc.
length: 4 | 6 | 8 | 10 | 12; // ...
}
Είναι δυνατόν να οριστεί αυτός ο τύπος πιο ακριβώς και χωρίς το όριο μήκους;
Απάντηση #1
Κατάφερα να δημιουργήσω ένα σύνολο 55 πλειάδων με μέγιστο μήκος - 110 στοιχεία. Αυτό σημαίνει πλειάδες με το ακόλουθο μήκος:
110 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 76 | 78 | 80 | ... 13 more ... | 108
Υπάρχει ένα όριο έως 110 στοιχεία στην πλειάδα
type MathCall = [
'+' | '-' | '/' | '*' | '>' | '<',
Expression,
Expression,
];
type Expression = number | string | boolean | MathCall;
type CallParams = [Expression, Expression]
/**
* It is not allowed to increase this number, at least in TS.4.4
*/
type MAXIMUM_ALLOWED_BOUNDARY = 110
type Mapped<
Arr extends Array<unknown>,
Result extends Array<unknown> = [],
Original extends any[] = [],
Count extends ReadonlyArray<number> = []
> =
(Count['length'] extends MAXIMUM_ALLOWED_BOUNDARY
? Result
: (Arr extends []
? []
: (Arr extends [infer H]
? [...Result, H, ...([] | Mapped<Original, [], [], [...Count, 1]>)]
: (Arr extends [infer Head, ...infer Tail]
? Mapped<[...Tail], [...Result, Head], Arr, [...Count, 1]>
: Readonly<Result>
)
)
)
)
// Main result
type CaseCallParams = Mapped<CallParams>
// TESTS
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
// credits goes to https://github.com/microsoft/TypeScript/issues/13298#issuecomment-468114901
type UnionToOvlds<U> = UnionToIntersection<
U extends any ? (f: U) => void : never
>;
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
type UnionToArray<T, A extends unknown[] = []> = IsUnion<T> extends true
? UnionToArray<Exclude<T, PopUnion<T>>, [PopUnion<T>, ...A]>
: [T, ...A];
type Result = UnionToArray<CaseCallParams>[3]['length']; // 8
type Result_ = UnionToArray<CaseCallParams>[4]['length']; // 10
type Result__ = UnionToArray<CaseCallParams>[10]['length']; // 12
type Result___ = UnionToArray<CaseCallParams>[12]['length']; // 26
type Result____ = UnionToArray<CaseCallParams>[20]['length']; // 42
type Result_____ = UnionToArray<CaseCallParams>[50]['length']; // 102
type Result______ = UnionToArray<CaseCallParams>[54]['length']; // 110
// should end with 0 , 2 , 4 , 6 , 8
type EvenNumbers = UnionToArray<CaseCallParams>[number]['length']
Παρακαλώ ενημερώστε με αν σας βολεύει.
Παιδική χαρά
Νομίζω ότι θα είναι δυνατό να αυξηθεί το όριο μετά την ενσωμάτωση της ουσιαστικής αναδρομής της Tail στην αξιολόγηση των συνθηκών τύπων
Επιπλέον, μπορείτε να ορίσετε μια συνάρτηση που θα επαληθεύει το μήκος του πλειάδας:
type EvenEnd = '0' | '2' | '4' | '6' | '8'
type IsEven<T extends `${number}`> =
(T extends `${infer Int}${infer Rest}`
? (
Rest extends ''
? (Int extends EvenEnd
? true
: false
)
: (Rest extends `${number}`
? IsEven<Rest>
: false
)
)
: false
)
{
type Test1 = IsEven<'80'> // true
type Test2 = IsEven<'9010'> // true
type Test3 = IsEven<'1'> // false
type Test4 = IsEven<'99999999'> // false
}
type EvenLength<T extends Expression[]> =
IsEven<`${T['length']}`> extends true
? T
: never
const evenTuple = <
T extends Expression,
Tuple extends T[]
>(tuple: EvenLength<[...Tuple]>) => tuple
evenTuple([1, 3]) // ok
evenTuple([1, 3, 4]) // error
Παιδική χαρά 2
Το άρθρο μου
ΕΝΗΜΕΡΩΣΗ
Μια άλλη λύση, η οποία σας επιτρέπει να δημιουργήσετε ένα tuple με 999 στοιχεία.
type Expression = number | string | boolean | CallExpression;
type CallExpression = MathCall | CaseCall;
type MathCall = [
'+' | '-' | '/' | '*' | '>' | '<',
Expression,
Expression,
];
type MAXIMUM_ALLOWED_BOUNDARY = 999
type Mapped<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result
: Mapped<N, [...Result, Result['length']]>
)
// 0 , 1, 2 ... 998
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>[number]
type Dictionary = {
[Prop in NumberRange as `${Prop}`]: Prop
}
type EvenEnd = '0' | '2' | '4' | '6' | '8'
type IsEven<T extends `${number}`> =
(T extends `${infer Int}${infer Rest}`
? (
Rest extends ''
? (Int extends EvenEnd
? true
: false
)
: (Rest extends `${number}`
? IsEven<Rest>
: false
)
)
: false
)
type Compare<Num extends number> =
Num extends number
? IsEven<`${Num}`> extends true
? Num
: never
: never
type EvenRange = Exclude<Compare<NumberRange>, 0>
type CaseCall<Exp = any> = {
[Prop in Exclude<NumberRange, 0>]?: Exp
} & { length: EvenRange }
const tuple: CaseCall<Expression> = [1, 1, 1, 1] as const // ok
const tuple2: CaseCall<Expression> = [1, 1, 1] as const // expected error
const handle = <
Exp extends Expression, Data extends Exp[]
>(
arg: [...Data],
...check: [...Data]['length'] extends EvenRange ? [] : [never]
) => arg
handle([1, 1, 1]) // expected error
handle([1, 1]) // ok
Παιδική χαρά
ΕΝΗΜΕΡΩΣΗ 2
Πιο εύκολη προσέγγιση για τη δημιουργία ένωσης πλειάδων με ζυγό μήκος:
type MAXIMUM_ALLOWED_BOUNDARY = 50
type Mapped<
Tuple extends Array<unknown>,
Result extends Array<unknown> = [],
Count extends ReadonlyArray<number> = []
> =
(Count['length'] extends MAXIMUM_ALLOWED_BOUNDARY
? Result
: (Tuple extends []
? []
: (Result extends []
? Mapped<Tuple, Tuple, [...Count, 1]>
: Mapped<Tuple, Result | [...Result, ...Tuple], [...Count, 1]>)
)
)
type Result = Mapped<[string, number]>
// 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24
type Test = Result['length']
Παιδική χαρά
