Open51

型力強化のためにtype-challengesをやってみた

toshinobutoshinobu

Easy Pick

Key in Kのところでちょっと考えた

解答

type MyPick<T, K extends keyof T> = {
  [Key in K]: T[Key]
}
toshinobutoshinobu

Tuple to Object

解答

type TupleToObject<T extends readonly any[]> = {
  [K in T[number]]: K extends infer U ? U : never
}

感想

元もの問題が、タプルをオブジェクトの型に変換するというもの

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

肝はK extends infer U ? U : neverで、Kの部分の方を推論してくれる
inferは... extends ... ? ... : ...という形式でしか使用できない

toshinobutoshinobu

First of Array

解答

type First<T extends any[]> = T extends [(infer U), ...any[]] ? U : never

感想

最初は

type First<T extends any[]> = T[0] extends infer U ? U : never

で対応したけど、これだとFirst<[]>の場合にundefinedになってしまってチェックに通らなかった
なので、上記のように対応
Tの0番目があれば推論して、できなければneverを返すようにした

toshinobutoshinobu

Length of Tuple

解答

type Length<T> = T extends { length: infer U } ? U : never

感想

inferの使い方がなんとなくわかってきた

toshinobutoshinobu

Exclude

解答

type MyExclude<T, U> = T extends U ? never : T

感想

これも結構簡単だった
Unionにおいてneverは無視されるので、Excludeしたいやつと合致した時はneverでfilterするようにした

toshinobutoshinobu

Awaited

解答

type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T

感想

再起的にするのが少し手間取った 
あとT extends Promise<infer U> じゃなく、T extends PromiseLike<infer U>にしないとダメ

toshinobutoshinobu

If

解答

type If<C, T, F> = C extends true ? T : F

感想

シンプルにextendsで対応

toshinobutoshinobu

Concat

解答

type Concat<T extends any[], U extends any[]> = [...T, ...U]

解答

こんなことできるのかなって思ったらできた
できたんだ...

toshinobutoshinobu

Includes

解答

type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ? Equal<First, U> extends true ? true : Includes<Rest, U> : false

感想

これはちょっと自分では思いつかなかった。。。

type Includes<T extends readonly any[], U> = U extends T[number] ? true : false

ここまでは単純に言ったのだが、

Includes<[{}], { a: 'A' }>
Includes<[{ readonly a: 'A' }], { a: 'A' }>

とかが通らなくて挫折した

toshinobutoshinobu

Push

解答

type Push<T extends any[], U> = [...T, U]

感想

単純だ...
むしろこれでいいの?

toshinobutoshinobu

Parameters

解答

type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer U) => any ? U : never

感想

inferの復習かな

toshinobutoshinobu

Get Return Type

解答

type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer U ? U : never

感想

今までのことをやればできる

toshinobutoshinobu

Omit

解答

type MyExclude<T, U> = T extends U ? never : T
type MyOmit<T, K extends keyof T> = {
  [P in MyExclude<keyof T, K>]: T[P]
}

感想

ExcludeEasy Pickの組み合わせ

toshinobutoshinobu

Readonly 2

解答

type MyExclude<T, U> = T extends U ? never : T
type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in K]: T[P]
} & {
  [Q in MyExclude<keyof T, K>]: T[Q]
}

感想

Omitの応用編
KをReadOnlyでそれ以外を元のままにして合体

toshinobutoshinobu

Deep Readonly

解答

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? T[K] extends Function ? T[K] : DeepReadonly<T[K]> : T[K]
}

感想

もっとスマートな方法がありそう
objectかつFunctionの時を例外としてる

toshinobutoshinobu

Tuple To Union

解答

type TupleToUnion<T extends any[]> = T[number]

感想

これはEasyなのでは?
よく使う気がする

toshinobutoshinobu

Chainable Options

解答

type Chainable<options = {}> = {
  option<TKey extends string, TVal>(key: TKey extends keyof options ? never : TKey, value: TVal): Chainable<Omit<options, TKey> & {
    [key in TKey] : TVal
  }>
  get(): options
}

感想

これほんとに難易度中...?
急に難しくなりすぎじゃない?

解説

const hoge = a
  .option('foo', 123)
  .option('bar', { value: 'Hello World' })
  .get()

例えば、上記のような場合を考える

この時、

const hoge = a

の時点での型はChainable<{}>

次にoption('foo', 123)を追加した時の返り値の型を考えてみる
option('foo', 123)を追加した時、option関数にはoption<"foo", number>というGenericsが渡される

この時の引数の方はkey: TKey extends keyof options ? never : TKey, value: TValの部分で解釈され、もし仮に既に同じオプション名が追加されていたら、keyがnever型になりエラーになる
ない場合はそのままTKey型として解釈される
今回の場合だとまだ'foo'というオプション名は追加されていないのでそのまま

{
  'foo': number
}

となる

最後にoption関数の返り値の型の部分
一個前のChainableのoptionsGenerics(一個までは何もしてないのでoptions = {})からTKey名のものを削除する(重複削除)、そして新しくTKey: TValのペアがoptionsに追加されたものをGenericsとして受け取ったChainableが出来上がる

この時点での型はChainable<{ 'foo': number }>
あとはこの作業の繰り返し

最後にget()する時は今まで追加してきたoptionsの型を返す

toshinobutoshinobu

Last of Array

解答

type Last<T extends any[]> = T extends [...infer Rest, infer U] ? U : never

感想

特になし

toshinobutoshinobu

Pop

解答

type Pop<T extends any[]> = T extends [...infer Rest, infer Pop] ? Rest : []

感想

else句のところは空配列でよかったのかな?

toshinobutoshinobu

Promise.all

解答

type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T

declare function PromiseAll<Values extends any[]>(values: readonly [...Values]): Promise<{
  [K in keyof Values]: MyAwaited<Values[K]>
}>

感想

MyAwaitedとの応用

toshinobutoshinobu

Type Lookup

解答

type LookUp<U extends { type: string }, T extends string> = U extends {
  type: T
} ? U : never

感想

特になし

toshinobutoshinobu

Trim Left

解答

type TrimLeft<S extends string> = S extends `${infer L}${infer R}` ? (L extends ' ' | '\n' | '\t' ? TrimLeft<R> : S) : S

感想

type TrimLeft<S extends string> = S extends `${infer L}${infer R}` 

の時、Genericsとして"str"を渡すとLにはsが、Rにはtrが代入される

toshinobutoshinobu

Trim

解答

type TrimStr = ' ' | '\n' | '\t'
type TrimRight<S extends string> = S extends `${infer L}${TrimStr}` ? `${TrimRight<L>}` : S
type TrimLeft<S extends string> = S extends`${TrimStr}${infer R}` ? `${TrimLeft<R>}` : S

type Trim<S extends string> = TrimLeft<TrimRight<S>> 

感想

左右で再起的にTrimしていく

toshinobutoshinobu

Capitalize

解説

type MyCapitalize<S extends string> = S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : ''

感想

最初の一文字をUppercaで大文字に、残りはそのままでjoin

toshinobutoshinobu

Replace

解答

type Replace<S extends string, From extends string, To extends string> = S extends `${infer Start}${From}${infer End}` ? From extends '' ? S : `${Start}${To}${End}` : S

感想

infer x Template Literalsの挙動をもうちょっと詳しく調べたい

toshinobutoshinobu

ReplaceAll

解答

type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer Start}${From}${infer End}` ? From extends '' ? S : `${Start}${To}${ReplaceAll<End, From, To>}` : S

感想

一回これでやってみたけどだめだった

type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer Start}${From}${infer End}` ? From extends '' ? S : ReplaceAll<`${Start}${To}${End}`, From, To> : S

この場合、'foobarfoobar''ob''b'に変えようとした時、再起的に置換していくのでfbarfbarになる
なので、後ろの部分だけを再起的に置換していく

toshinobutoshinobu

Append Argument

解答

type AppendArgument<Fn extends (...args: any[]) => any, A> = Fn extends (...args: infer Params) => infer Returns ? (...args: [...Params, A]) => Returns : never

感想

引数と戻り値をそれぞれinferで推測し、引数には第二引数でもらったパラメータを追加して返す

toshinobutoshinobu

Permutation

解答

type Permutation<Union, Item = Union> =
    [Union] extends [never]
      ? []
      : Item extends Item
        ? [Item, ...Permutation<Exclude<Union, Item>>]
        : never

感想

むっず

toshinobutoshinobu

Length of String

解答

type StringToArray<S extends string> = S extends `${infer Start}${infer Rest}`
  ? [Start, ...StringToArray<Rest>] : []
type LengthOfString<S extends string> = StringToArray<S>['length']

感想

StringをArrayにしてから'length'を取得
ちなみに

type LengthOfString<S extends string> = S['length']

だとnumberになる。なんでだろうね

toshinobutoshinobu

Flatten

解答

type Flatten<Array extends any[], Prev extends any[] = []> = Array extends [infer First, ...infer Rest]
  ? First extends any[]
    ? Flatten<[...First, ...Rest], Prev>
    : Flatten<[...Rest], [...Prev, First]>
  : Prev

感想

Genericsの第二引数を保存先として使用しているのが肝

toshinobutoshinobu

Append to object

解答

type AppendToObject<T, U extends string, V> = {
  [Key in U | keyof T]: Key extends keyof T ? T[Key] : V
}

感想

type AppendToObject<T, U extends string, V> = T extends infer Obj ?
Obj & {
  [Key in U]: V
} : never

じゃだめだった

[Key in U | keyof T]って書き方を初めて知った

toshinobutoshinobu

Absolute

解答

type Absolute<T extends number | string | bigint> = `${T}` extends `${infer First}${infer Rest}`
  ? First extends '-'
    ? Rest
    : `${T}`
  : never

感想

inferだいぶ慣れた

toshinobutoshinobu

String to Union

解答

type StringToArray<T extends string> =  T extends `${infer First}${infer Rest}`
  ? [First, ...StringToArray<Rest>]
  : []
type StringToUnion<T extends string> = StringToArray<T>[number]

感想

一度、Stringを一文字ごとの配列に変換してからUnionへ変換した

toshinobutoshinobu

Merge

解答

type Merge<F, S> = {
  [Key in keyof F | keyof S]: Key extends keyof S ? S[Key] : Key extends keyof F ? F[Key] : never
}

感想

同じKey名がある場合、FがSに上書きされるようにしないといけないので順番に注意

toshinobutoshinobu

Kebab Case

解答

type KebabCase<S extends string> = S extends `${infer First}${infer Rest}` 
  ? Rest extends Uncapitalize<Rest> 
    ? `${Uncapitalize<First>}${KebabCase<Rest>}` 
    : `${Uncapitalize<First>}-${KebabCase<Rest>}`
  : S

感想

先頭の文字(First)とそれ以外の文字(Rest)の分割
Restの先頭が小文字だったら、Firstを小文字にして残りの文字列をKabebCaseに渡す
Restの先頭が大文字だったら、Firstを小文字にしてハイフンを追加。RestをKebabCaseに渡す

toshinobutoshinobu

Diff

解答

type Diff<O, O1> = Omit<O & O1, keyof (O | O1)>

感想

シンプルだけど意外と苦戦
OO1が合体したオブジェクトからkeyof (O | O1)で和集合のキーを指定

toshinobutoshinobu

AnyOf

解答

type AnyOf<T extends readonly any[]> = T[number] extends '' | 0 | false | undefined | null | [] | { [key: string]: never } ? false : true

感想

とりあえず、Falsyなものを列挙してそれに当てはまればFalse、そうでなければTrueと判定

toshinobutoshinobu

IsNever

解答

type IsNever<T> = [T] extends [never] ? true : false

感想

Permutationのところでやったのですぐに分かった

ちなみに

type IsNever<T> = T extends never ? true : false

とすると

type Result = IsNever<never>

の時に、Result = neverとなり判定に失敗する

toshinobutoshinobu

IsUnion

解答

type IsUnionInternal<T, P = T> = T extends infer Union
  ? P extends Union
    ? true
    : false
  : never

type IsUnion<T> = IsUnionInternal<T> extends true ? false : true 

解説

https://qiita.com/Quramy/items/b45711789605ef9f96de#conditional-types

T extends infer Union

T(e.g 1 | 2 | 3)をUnionに推論する

? P extends Union

TをUnionに推論できた時、P(1 | 2 | 3)がUnionのサブタイプである場合を確認
PはUnionなのでUnionTypesは分解され

P extends Union(1 extends Union ...) | (2 extends Union ...) | (3 extends Union ...)
と分解される
もし条件分岐がtrueならtrueをそうでないならfalseを返す

toshinobutoshinobu

ReplaceKeys

解答

type ReplaceKeys<Source, KeysUnion, Y> = {
  [Key in keyof Source]: Key extends KeysUnion ? Key extends keyof Y ? Y[Key] : never : Source[Key]
}

解説

今回はReplaceのみなので

[Key in keyof Source]

で元ObjectのKeyのみを抽出

Key extends KeysUnion ? Key extends keyof Y ? Y[Key] : never : Source[Key]

ObjectのKeyが第二引数のUnionの一部であるかつ元Objectのキーと第三引数のObjectのキーが一致している場合、置換後の方のObjectのValueを使い、ない場合はnever
そもそも置換後のObjectに対象のキーがない場合は元ObjectのValueをそのまま使用する

toshinobutoshinobu

Remove Index Signature

解答

type RemoveIndexSignature<T> = {
  [K in keyof T as PropertyKey extends keyof T[K] ? never : K]: T[K];
}

解説

PropertyKeyは以下のように定義されている

declare type PropertyKey = string | number | Symbol;
toshinobutoshinobu

Percentage Parser

解答

type Tail<A extends string> = A extends `${infer Middle}${LastStr}`
  ? [Middle, LastStr]
  : A extends `${infer Middle}`
    ? [Middle, ""]
    : ["", ""]
type PercentageParser<A extends string> = A extends `${infer First}${infer Rest}`
  ? First extends FirstStr
    ? [First, ...Tail<Rest>]
    : ["", ...Tail<`${First}${Rest}`>]
  : ["", "", ""]

解説

第一引数(e.g. "+100%")を先頭一文字F(First)とその他(Rest)に分解
Firstが+もしくは-の場合、[First, ...Tail<Rest>]
それ以外の場合だとFirstが数字になるので、["", ...Tail<{First}{Rest}>]として処理する

Firstを処理したあと、Tailで処理する(この時点では+-は含まないstringが渡ってくる)
Tailに渡っていた第一引数を%とそれ以外(Middle)に分解する
もし%が含まれており、分解できた場合そのまま、数字の部分(Middle)と記号の部分(LastStr)を返す
もし%が含まれていない場合は、記号の部分を空文字で返す
数字の部分さえない場合はMiddleの部分も空文字にして返す

toshinobutoshinobu

Drop Char

解答

type DropRight<S extends string, C extends string> = S extends `${infer L}${C}` ? `${DropRight<L, C>}` : S
type DropLeft<S extends string, C extends string> = S extends`${C}${infer R}` ? `${DropLeft<R, C>}` : S
type DropMiddle<S extends string, C extends string> = S extends `${infer L}${C}${infer R}` ? `${DropMiddle<L, C>}${DropMiddle<R, C>}` : S
type DropChar<S extends string, C extends string> = DropLeft<DropMiddle<DropRight<S, C>, C>, C>

解説

Trimの応用

toshinobutoshinobu

MinusOne

解答

type Tuple<L extends number, T extends unknown[] = []> = T["length"] extends L
  ? T
  : Tuple<L, [...T, unknown]>;

type FromToTuple<L extends number, From extends unknown[] = [], To extends unknown[] = []> = From['length'] extends L
  ? From // FromのlengthがLと同じ時
  : From['length'] extends To['length'] // FromのlengthがLと違う時かつ、FromとToのlengthが同じ時
    ? never // 終わり
    : FromToTuple<L, [unknown, ...From], To> // Fromに要素を一つ追加して再帰

type TupleMax = Tuple<999>

type SuperTuple<L extends number, C extends unknown[] = [], P extends unknown[] = []> = C["length"] extends L
  ? C // 既に配列の長さがLと同じ時
  : FromToTuple<L, P, C> extends never // まだ長さを満たしてない時かつ、LとPのlengthが同じかつ、PとCのlengthが同じ
    ? SuperTuple<L, [...TupleMax, ...C], C>
    : FromToTuple<L, P, C>

type Minus<A extends number> = SuperTuple<A> extends [infer _, ... infer R extends unknown[]] ? R['length'] : never

type MinusOne<T extends number> =
  T extends 0
    ? -1
    : Minus<T>

解説

難しかった
普通にやると999以上の数字に対応できないので工夫が必要

toshinobutoshinobu

PickByType

解答

type PickByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K] extends U ? T[K] : never
}

解説

特になし

toshinobutoshinobu

StartsWith

解答

type StartsWith<T extends string, U extends string> = T extends `${U}${infer Rest}` ? true : false

解説

特になし

toshinobutoshinobu

EndsWith

解答

type EndsWith<T extends string, U extends string> = T extends `${infer _}${U}` ? true : false

解説

特になし

toshinobutoshinobu

PartialByKeys

解答

type Flatten<T> = {
  [P in keyof T]: T[P]
}

type PartialByKeys<T, K extends keyof T = keyof T> = Flatten<{
  [P in Exclude<keyof T, K>]: T[P]
} & {
  [P in K]?: T[P]
}>

解説

type PartialByKeys<T, K extends keyof T = keyof T> = {
  [P in Exclude<keyof T, K>]: T[P]
} & {
  [P in K]?: T[P]
}

だと

type Result = {
    address: string;
} & {
    name?: string | undefined;
    age?: number | undefined;
}

となってしまうので、一度合体させてから平坦にしている