javascript - Typedefinisjon for array med minimums- og makslengde i Typescript


Hvordan kan jeg lage en typedefinisjon for array med minimums- og maksimumslengde, som i:

var my_arr:my_type<type, {start: 1, end: 3}>;

my_arr = [1] - pass
my_arr = [1,2] - pass
my_arr = [1,2,3] - pass
my_arr = [1,2,3,4] - fail
my_arr = [1, "string"] - fail

Dvs. oppgi typen til elementene i arrayet og en startindeks og sluttindeks for lengden slik at hvis lengden er mindre enn startindeksen, mislykkes det, eller hvis lengden er større enn sluttindeksen, mislykkes det.

Jeg har funnet denne løsningen for et array med fast lengde på nettet:

type SingleLengthArray<
  T,
  N extends number,
  R extends readonly T[] = []
> = R["length"] extends N ? R : SingleLengthArray<T, N, readonly [T, ...R]>;

Og jeg klarte å lage en type som fungerer hvis lengden på arrayet er enten lik startindeksen eller sluttindeksen, men ingenting imellom:

type MultipleLengthArray<
  T,
  N extends { start: number; end: number },
  R extends readonly T[] = []
> = SingleLengthArray<T, N["start"], R> | SingleLengthArray<T, N["end"], R>;

Det jeg tror vil fungere hvis det er mulig, er å lage et array av tall fra startindeksen opp til sluttindeksen (dvs. startindeks=1, sluttindeks=5 => arrayet ville være [1,2,3,4,5]) og iterere over arrayet og som

type MultipleLengthArray<T, N extends {start: number, end:number}, R extends readonly T[] = []> = for each value n in array => SingleLengthArray<T, n1, R> | SingleLengthArray<T, n2, R> | ... | SingleLengthArray<T, n_last, R>

Vennligst gi meg beskjed hvis det finnes en måte å gjøre dette på, takk.

Svar #1

Jeg tolker dette som å lete etter en typefunksjon vi kan kalle TupMinMax<T, Min, Max>, som løser seg til en tuppeltype der hvert element er av typen T, og hvis lengde må være mellom Min og Max inkludert. Vi kan representere dette som en enkelt tuppeltype med valgfrie elementer på hvert indeks større enn Min og mindre enn eller lik Max. (Med mindre du slår på --exactOptionalPropertyTypes kompilatoralternativet, vil dette også tillate undefined verdier for disse valgfrie egenskapene, men jeg antar at det ikke er noe stort problem). Så du vil for eksempel at TupMinMax<number, 1, 3> skal være [number, number?, number?].

Her er en tilnærming:

type TupMinMax<
  T, Min extends number, Max extends number,
  A extends (T | undefined)[] = [], O extends boolean = false
  > = O extends false ? (
    Min extends A['length'] ? TupMinMax<T, Min, Max, A, true> : 
    TupMinMax<T, Min, Max, [...A, T], false>
  ) : Max extends A['length'] ? A : 
    TupMinMax<T, Min, Max, [...A, T?], false>;

Dette er en hale-rekursiv betinget type, hvor TupMinMax<T, Min, Max> har noen ekstra parametere A og O for å fungere som akkumulatorer for å lagre mellomliggende tilstand. Spesifikt vil A lagre tuppelresultatet så langt, mens O vil være enten true eller false som representerer om vi har gått inn i den valgfrie delen av tuplen. Det starter med false og blir true senere.

Den første betingelsestesten er O extends false ? (...) : (...). Hvis O er false har vi ennå ikke nådd minimumslengden, og elementene bør være påkrevde. Deretter sjekker vi Min extends A['length'] for å se om akkumulatoren har nådd minimumslengden. Hvis det er tilfellet, bytter vi umiddelbart O til true med samme A-akkumulator. Hvis ikke, legger vi til et påkrevd T-element til slutten av A. Hvis O ikke er false, er det true, og vi sjekker deretter Max extends A['length'] for å se om akkumulatoren har nådd makslengden. Hvis det er tilfellet, er vi ferdige og evaluerer til A. Hvis ikke, legger vi til et valgfritt T-element til slutten av A.

La oss teste det:

type OneTwoOrThreeNumbers = TupMinMax<number, 1, 3>;
// type OneTwoOrThreeNumbers = [number, number?, number?]

let nums: OneTwoOrThreeNumbers;
nums = []; // error
nums = [1]; // okay
nums = [1, 2]; // okay
nums = [1, 2, 3]; // okay
nums = [1, 2, 3, 4]; // error
nums = [1, "two", 3]; // error

type BetweenTenAndThirtyStrings = TupMinMax<string, 10, 30>;
/* type BetweenTenAndThirtyStrings = [string, string, string, string, 
     string, string, string, string, string, string, string?, string?, 
     string?,  string?, ... 15 more ...?, string?] */
let strs: BetweenTenAndThirtyStrings;
strs = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14"]

Ser bra ut. Den hale-rekursjonen betyr at kompilatoren bør kunne håndtere en rekursjonsdybde på ~1 000 nivåer, så hvis rekkevidden av tuppel-lengden din er så stor som flere hundre, bør det kompilere greit.

Merk at slike rekursive typer kan være skjøre og utsatt for ubehagelige kanttilfeller. Hvis du liker å torturere deg selv og kompilatoren din, kan du prøve å sende noe annet enn ikke-negative heltall som Min og Max, eller sende inn en Min som er større enn Max. Rekursjonens basistilfelle vil aldri bli nådd, og kompilatoren vil, hvis du er heldig, klage på rekursjonsdybde; og hvis du ikke er heldig, vil den bruke mye CPU og gjøre datamaskinen din varm:

// 🔥💻🔥 followed by 
// Type instantiation is excessively deep and possibly infinite.
// type Oops = TupMinMax<any, 10, 1>; 
// type Oops2 = TupMinMax<any, 3.5 4>;

Så vær forsiktig.

Lenke til kode på lekeplassen

Svar #2

Du kan kanskje bruke en Tuple med valgfrie egenskaper. Tupler har en spesifisert lengde (som tillater at egenskapene kan være valgfrie).

enum E { V, A, S }

type TupleOfE = [ E, E, E, E?, E? ]

const e: TupleOfE = [ E.V, E.A, E.S, E.S, E.S ]

console.log(e)
console.log(e.filter(e => e === E.S))

Relaterte artikler