Type Challengesを解いていく(中級)
の続きから。
自分の解答
かなり混乱した。以前解いた Exclude や Pick の知識が求められ、総合力を鍛えられた気がする。
結局、ここで自分は Exclude の理解がまだ甘いことを再認識できた。
最終的に解答は以下のようになった。
type MyExclude<U, T> = U extends T ? never : U;
type MyOmit<T, K extends keyof T> = { [P in MyExclude<keyof T, K>]: T[P] };
as
を使った書き方もあるようで、以下が参考になった。
type-challenges をやる
自分の解答
type MyReadonly<T> = { readonly [K in keyof T]: T[K] }
type MyReadonly2<T, K extends keyof T = keyof T> = MyReadonly<Pick<T, K>> & Omit<T, K>
Pick
で指定したものだけReadonly
にして、Pick
していないものをOmit
で補うという発想。
自分の解答
type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K]
};
だとダメだった。
この解答を拡張させるなら、以下の解答が良さそう。
type DeepReadonly<T> = {
readonly [K in keyof T] : T[K] extends Record<string,unknown> | Array<unknown> ? DeepReadonly<T[K]> : T[K];
}
Issues · type-challenges/type-challenges
上記の解答でRecord<string, any>
だとダメでRecord<string, unknown>
だと OK なのはなぜなのかはあまりよくわかっていない・・・。
また、他の解答として
type DeepReadonly<T> = {
readonly [k in keyof T] : keyof T[k] extends never ? T[k] : DeepReadonly<T[k]>
}
のようにキーがあるかどうかの判定もわかりやすいと思った。
9 - Deep Readonly · Issue #10261 · type-challenges/type-challenges
自分の解答
type TupleToUnion<T extends unknown[]> = T[number];
見覚えがあるなと思ったら Tuple to Object だった。こっちのほうが簡単じゃね?
自分の解答
ぶっちゃけ意味不明だった。ここまで来ると黒魔術感がすごい。key
にジェネリクスを使うことと、option
が値を返す際にkey
をどこかに追加していくことは何となく分かったが、具体的な解法は思いつかなかった。
実装の手順は Chainable Options が参考になった。
type Chainable<O = {}> = {
option<K extends string, V>(key: K, value: V): Chainable<O & { [P in K]: V }>;
get(): O;
};
これは上記記事による最終的なコードだが、これだとkey
の重複チェックができていないため、12 - Chainable Options - type-challenges/type-challenges を参考にさらに書き換えてみた。
type Chainable<O = {}> = {
option<K extends string, V>(key: K extends keyof O ? never : K, value: V): Chainable<O & { [P in K]: V }>;
get(): O;
};
もちろん、以下のコードも動作する。
type Chainable<T extends object = {}> = {
option<K extends keyof any, V>(key: K extends keyof T ? never : K, value: V): Chainable<T & Record<K, V>>
get(): T
}
自分の解答
type Last<T extends any[]> = T extends [...T, infer U] ? U : never
のような雰囲気かと思ったが、ちょっと違っていた・・・(上記は動作しない)。
type Last<T extends any[]> = T extends [...infer V, infer R] ? R : never;
15 - Last of Array · Issue #10687 · type-challenges/type-challenges
type Last<T extends any[]> = T extends [...any, infer K] ? K : never
15 - Last of Array · Issue #10415 · type-challenges/type-challenges
上記の...infer V
および...any
は覚えておきたい。
今回のケースだと...infer V
のV
は使っていないので、_V
とするほうが適切かもしれない。
...any
は条件を厳しくしたい場合は...string[]
のような書き方もいける。また、[...any]
とany[]
は等しい。
自分の解答
type Pop<T extends any[]> = T extends [...infer P, any] ? P : never;
前回の問題と似ていたため、すぐに終わりました。
自分の解答
declare function PromiseAll<T>(values: T): Promise<T>;
のように、そんなシンプルにはいかなかった。
解答例は以下の通りで、as const
に対応するためのreadonly [...T]
や配列に対しての[K in keyof T]
(Mapped Types)、およびPromise<infer R>
でPromiseの型を取得するのがキモだと感じた。
declare function PromiseAll<T extends any[]>(
values: readonly [...T]
): Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K] }>;
自分の解答
type LookUp<U extends Record<'type', unknown>, T extends U[keyof U]> =
T extends U['type'] ? U : never;
のような雰囲気かと思ったが、全然違っていた...。
type LookUp<U, T> = U extends {type: T} ? U : never;
が解答案で、めちゃくちゃシンプルだと思った。自分は文字列どうしを比較しようとしていたが、確かにオブジェクトの比較のほうがすっきり記述できそうだと感じた。
ただ、似たような理論で文字列の比較の
type LookUp<U extends { type: string }, T> = U['type'] extends T ? U : never;
が動作しないのはなんでだろう?という気がするが...。
自分の解答
そもそもTemplate Literal Typesに対するinfer
の使い方を知らなかったため手探りだったが、以下のようにすれば最初の一文字だけ空白のケースに対応できることはわかった。
type TrimLeft<S extends string> = S extends `${infer S1}${infer S2}`
?
S2
: never
;
あとはループしていけばいけるのかな...と思ったが、ループする方法がわからず断念。
以下が解答例で、なるほど確かに空白を渡せばよかったのか...と納得した。
type Space = ' ' | '\n' | '\t';
type TrimLeft<S extends string> = S extends `${Space}${infer Rest}` ? TrimLeft<Rest> : S;
今回はテストケースに'\n'
なども含まれるため、Space
として対応する必要がある。
自分の解答
おそらくTrim Leftの完全版?が来ると思っていたが、やはり来た。
以下のように、左側の空白を取り除いた後に右側を取り除くというロジックで成功した。
type Trim<S extends string> = S extends `${Space}${infer V}` ? Trim<V> :
S extends `${infer U}${Space}` ? Trim<U> : S;
ただ、解答例には
type Space = ' ' | '\n' | '\t';
type Trim<S extends string> = S extends `${Space}${infer Rest}` | `${infer Rest}${Space}` ? Trim<Rest> : S;
のようなものもあり、たしかにこの方がシンプルで良いな、と思った。