TypeScriptの配列型で基本的な配列操作関数を模倣する
TypeScriptのversionが上がるごとに、より簡潔に書けるようになっているので、もしかしたら古いかもしれない

Head
ts type Head<T extends unknown[]> =
T extends [] ? never : T[0];
いろんな書き方がある
使用例.tstype Head1 = Head<[]>; // never
type Head2 = Head<[1]>; // 1
type Head3 = Head<[1, 2], 0>; // 1
never
を返すのではなく、そもそも空配列を許容しないようにしても良い
tstype Head<NonEmptyList extends [unknown, ...unknown[]]> = NonEmptyList[0];
使用例.tstype Head1 = Head<[]>; // error
type Head2 = Head<[1]>; // 1
Last
last :: [a] -> a
最後の要素を取り出す
inferを使って書く
tstype Last<T extends unknown[]> =
T extends [...unknown[], infer L] ? L : never;
lengthを使って書く
tstype Last<T extends unknown[]> = [unknown, ...T][T['length']];
[unknown, ...T]
とすることで、配列の要素を1つ増やしている部分がポイント
要素を増やさずにlengthすると、常にundefinedになってしまう
tstype Last<T extends unknown[]> = T[T['length']];
type Last<T extends unknown[]> = T[T['length'] - 1]; // こう書きたい
TS v3.0の時点では複雑な書き方をする必要があった
ref
Init
tstype Init<T extends unknown[]> =
T extends [...infer I, unknown] ? I : never;
使用例.tstype Init1 = Init<[]>; // never
type Init2 = Init<[1]>; // []
type Init3 = Init<[1, 2, 3]>; // [1,2]
Tail
...A
型のPR
...A
型のproposal
↓全体的に書き方が古い。もっと簡潔に書ける
head以外を返す
tstype Tail<T extends any[]> = ((...x: T) => void) extends (
x: any,
...xs: infer XS
) => void ? XS : never;
変態的すぎる

headと同じ用に書くと以下のようになりそうだがうまく行かない
tstype Tail<T extends any[]> = T extends [any, ...(infer XS)[]] ? XS[] : never;
...(infer XS)
こういうのが書けない
なので関数型 (x: any, ...xs: infer XS) => void
を使うことで実現する
左辺の extends
のあとの関数型 ( x: any, ...xs: infer XS) => void
は
( x: infer X, ...xs: infer XS) => void
とかにしても良い
とはいえどのみち X
は特に使わないのでanyのままでもいい
使用例.tstype Tail1 = Tail<[]>; // []
type Tail2 = Tail<[1]>; // []
type Tail3 = Tail<[1, 2, 3]>; // [2,3]
cons :: a -> [a] -> [a]
リストの先頭に任意の型を1つ加える
tstype Cons<Elm, T extends unknown[]> = [Elm, ...T]
使用例.tstype Cons1 = Cons<1, [2,3]> // [1,2,3]
type Cons2 = Cons<'a', [2,3]> // ['a',2,3]
このページでは関数型を使って書いているが、versionが上がってシンプルに書けるようになった
reverse :: [a] -> [a]
リストを反転する
tstype Reverse<L extends any[], X extends any[] = []> = {
0: X;
1: Reverse<Tail<L>, Cons<Head<L>, X>>;
}[L extends [] ? 0 : 1];
使用例.tstype Reve1 = Reverse<[]>; // []
type Reve2 = Reverse<[1]>; // [1]
type Reve3 = Reverse<[1, 2, 3]>; // [3,2,1]
merge :: [a] -> [a] -> [a]
tstype Merge<A extends any[], B extends any[], R extends any[] = []> = {
0: Reverse<R>;
1: Merge<Tail<A>, B, Cons<Head<A>, R>>;
2: Merge<A, Tail<B>, Cons<Head<B>, R>>;
}[A extends [] ? (B extends [] ? 0 : 2) : 1];
使用例.tstype Merg1 = Merge<[], []>; // []
type Merg2 = Merge<[1, 2], []>; // [1,2]
type Merg3 = Merge<[], [1, 2]>; // [1,2]
type Merg4 = Merge<[1, 2], [3, 4, 5]>; // [1,2,3,4,5]
take :: number -> [a] -> [a]
tstype Take<N extends number, T extends any[], R extends any[] = []> = {
0: Reverse<R>;
1: Take<N, Tail<T>, Cons<Head<T>, R>>;
}[T extends [] ? 0 : R["length"] extends N ? 0 : 1];
使用例.tstype Take1 = Take<2, []>; // []
type Take2 = Take<2, [1, 2, 3]>; // [1,2]
type Take3 = Take<0, [1, 2, 3]>; // []
drop :: number -> [a] -> [a]
tsexport type Drop<N extends number, T extends any[], R extends any[] = []> = {
0: T;
1: Drop<N, Tail<T>, Cons<Head<T>, R>>;
}[T extends [] ? 0 : R["length"] extends N ? 0 : 1];
使用例.tstype Drop1 = Drop<0, []>; // []
type Drop2 = Drop<0, [1, 2, 3]>; // [1,2,3]
type Drop3 = Drop<1, [1, 2, 3]>; // [2,3]
type Drop4 = Drop<5, [1, 2, 3]>; // []
flat :: [[a]] -> [a]
tstype Flat<T extends any[][], R1 extends any[] = [], R2 extends any[] = []> = {
0: Reverse<R2>;
1: Flat<Tail<T>, Head<T, []>, R2>;
2: Flat<T, Tail<R1>, Cons<Head<R1>, R2>>;
}[T extends [] ? (R1 extends [] ? 0 : 2) : R1 extends [] ? 1 : 2];
使用例.tstype Flat1 = Flat<[]>; //[]
type Flat2 = Flat<[[]]>; //[]
type Flat3 = Flat<[[1, 2], [], [3]]>; //[1,2,3]
replicate :: number -> a -> [a]
tsexport type Replicate<N extends number, T, R extends unknown[] = []> = {
0: R;
1: Replicate<N, T, Cons<T, R>>;
}[R['length'] extends N ? 0 : 1];
使用例.tstype Rep1 = Replicate<2, 3>; // [3, 3];
type Rep2 = Replicate<0, 2>; // [];
type Rep3 = Replicate<3, number>; // [1, 2, 3];
type Rep4 = Replicate<5, string>; // ['1', '2', '3', '4', '5'];
atLeast :: number -> a -> [a]
replicateの「少なくともN個ある」版
tstype AtLeast<N extends number, T> = AtLeastRec<N, T, T[], []>;
type AtLeastRec<Num, Elm, T extends unknown[], C extends unknown[]> = {
0: T;
1: AtLeastRec<Num, Elm, Cons<Elm, T>, Cons<unknown, C>>;
}[C extends { length: Num } ? 0 : 1];