Open36

type-challenges - easy

tegurintegurin

Pick

https://github.com/type-challenges/type-challenges/blob/master/questions/4-easy-pick/README.md

できた

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

keyof

https://www.typescriptlang.org/docs/handbook/2/keyof-types.html

The keyof operator takes an object type and produces a string or numeric literal union of its keys. The following type P is the same type as “x” | “y”:

keyof は、string または numeric literal をユニオン型で返す。

e.g.

type User = { id: number; name: string; age: number }
type UserKeys = keyof User; // 'id' | 'name' | 'age'
tegurintegurin

Mapped Types

https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

A mapped type is a generic type which uses a union of PropertyKeys (frequently created via a keyof) to iterate through keys to create a type:

型のプロパティのキー(s)をユニオン型で返し、ループして型を作成する。

syntax
Property in keyof Type

  • keyof Type
    • Typeのプロパティをユニオン型で返す
  • Property in UnionType
    • Propertyにユニオン型の要素が順に入ってくる

e.g.

type User = { id: number; name: string; age: number }

// User型の値の方をnumberにコンバートする
type ValToNum<Type> = {
    [P in keyof Type]: number
}
tegurintegurin

Readonly

https://github.com/type-challenges/type-challenges/blob/master/questions/7-easy-readonly/README.md

できた

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K]
}
tegurintegurin

readonly

https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers

There are two additional modifiers which can be applied during mapping: readonly and ? which affect mutability and optionality respectively.

readonly: mutability
?: optionality

You can remove or add these modifiers by prefixing with - or +. If you don’t add a prefix, then + is assumed.

+, -でmodifiers (readonly, ?) の付け外しが可能。

syntax
それぞれのmodifiersのprefixとしてつける。

e.g.

type User = { id: string; name: string; age?: number }

type OptionalProperties<T> = {
  [K in keyof T]+?: T[K]
}

type RequiredProperties<T> = {
  [K in keyof T]-?: T[K]
}


const optionalUser: OptionalProperties<User> = {
  name: 'john'
}

// Property 'age' is missing in type '{ id: string; name: string; }' but required in type 'RequiredProperties<User>'.
const requiredUser: RequiredProperties<User> = {
  id: 'randomstring',
  name: 'john'
}
tegurintegurin

Tuple to Object

https://github.com/type-challenges/type-challenges/blob/master/questions/11-easy-tuple-to-object/README.md

できなかった

type TupleToObject<T extends readonly any[]> = {
  [K in T[number]]: K
}
tegurintegurin

T[number]って発想がなかった。

Indexed Access Types

https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html

Another example of indexing with an arbitrary type is using number to get the type of an array’s elements. We can combine this with typeof to conveniently capture the element type of an array literal:

任意の型をインデックスとしてnumberを使うことで、配列の型を返す。
typeofとの組み合わせで、配列の要素を取得する

  • arbitrary: 任意の
tegurintegurin

tuple to union

  1. typeofでtupleの型を返す
  2. indexed access typesの [number]を使って、1をユニオン型へ
const tuple = ['javascript', 'typescript', 'python', 'java'] as const

// type TypeOfTuple = readonly ["javascript", "typescript", "python", "java"]
type TypeOfTuple = typeof tuple

// type Languages = "javascript" | "typescript" | "python" | "java"
type Languages = TypeOfTuple[number]
tegurintegurin

ユニオン型にすれば
[K in Union] が使えるので...

再掲になるががこういう回答ができる

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

// 用意されてるテスト
type cases = [
  Expect<Equal<TupleToObject<typeof tuple>, { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y'}>>,
]
  • readonly any[] は tupleの型を表現してる
  • [K in T[number]T[number]はユニオン型を返すので、それぞれの要素が入ってくる
tegurintegurin

First of Array

https://github.com/type-challenges/type-challenges/blob/master/questions/14-easy-first/README.md

できた

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

Conditional Types

When the type on the left of the extends is assignable to the one on the right, then you’ll get the type in the first branch (the “true” branch); otherwise you’ll get the type in the latter branch (the “false” branch).

A extends B ? true : false

型Aが型Bにアサイン可能かどうかをチェックする

例えば...

type answer1 = 64 extends number ? true : false
type answer2 = number extends 64 ?  true : false

answer1 64というnumeric literalはnumberにアサイン可能
answer2 numberは64 にはアサインできない

アサイン: 代入

const value1: 64 = 6
// Type '6' is not assignable to type '64'
const value2: number = 64
// assignable

extends keyword 条件式で使われるけど、equality checkではない。

個人的には === とか !== とか使えるともっと直感的なんだけどな。。
型システムだから型がassignableかどうかで判断するのがメイクセンスな気もするけど。。

e.g.

type answer1 = 64 extends number ? true : false
// true
type answer2 = number extends 64 ? true : false
// false
type answer3 = string[] extends any ? true : false
// true
type answer4 = string[] extends any[] ? true : false
// true
tegurintegurin

3回目やった時の解法
別解

type First<T extends any[]> = T extends [first: infer First, ...rest: any] ? First : never
tegurintegurin

Exclude

https://github.com/type-challenges/type-challenges/blob/master/questions/43-easy-exclude/README.md

できなかった
1回目できたの謎

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

TUともにユニオン型が入る。
これがしっくりきてなかった。ユニオン型ならin使って反復処理した上で条件分岐って考えた。

https://zenn.dev/link/comments/5f2bf0bd79ce5f

この課題を解くにあたり重要になるポイントは Conditional Types が分配法則(Distributive) に従うと
いう点です。Conditional Types の条件部において T extends U の T が ユニオン型である場合 T に対して反復処理を行い各要素に条件を適用します。

なるほど。明示的に反復処理をさせなくてもやってくれるんだ。

tegurintegurin

Distributive Conditional Types

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types

When conditional types act on a generic type, they become distributive when given a union type.

条件部分で、ジェネリックな型としてユニオン型が渡された時、分配法則になる。

T extends any で、Tstring | numberの場合 それぞれの要素に対して処理が行われる

e.g.

type ToArray<T> = T extends any ? T[] : never
type StrArrOrNumArr = ToArray<string | number>
// type StrArrOrNumArr = string[] | number[]
tegurintegurin

これ1回目、分配法則とか気にせず T extends U ? never : Tしたらコンパイルエラー出なかったから、お、できたと思って、スルーしてたなきっと。

tegurintegurin

Awaited

https://github.com/type-challenges/type-challenges/blob/master/questions/189-easy-awaited/README.md

できなかった。
infer + 再帰の問題だなとは思った。

type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
tegurintegurin

Promiseじゃなくなるまで反復処理して、Promiseじゃなくなったらその値を返す。

tegurintegurin

Includes

https://github.com/type-challenges/type-challenges/blob/master/questions/898-easy-includes/README.md

できなかった

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

tegurintegurin

最初こう書いた

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

単純にUがT[number] に代入可能ならtrue

これだと以下のようなケースに対応できない

  Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,

false extends booleanは代入可能なのでtrueとして評価される

tegurintegurin

Recursive Conditioanl Types

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types

Keep in mind that while these recursive types are powerful, but they should be used responsibly and sparingly.
節度を持って使うべき

But apart from being computationally intensive, these types can hit an internal recursion depth limit on sufficiently-complex inputs. When that recursion limit is hit, that results in a compile-time error.

計算量が多いということもあるが、内部的に再帰の深さ制限に引っかかり、コンパイルエラーを引き起こす可能性がある。

ただ、type-challengeにおいて、反復処理が必要になりそうなときはこれが使えるというのが頭にあると良さそう

反復処理をしたいときは再帰的に書けるというのを念頭に置いておくとアプローチしやすいかもしれない。

tegurintegurin

型の先頭から順々(再帰的)に比較していってマッチするものが来れば trueを返す。最終的に残り要素が一つになった時点で T extends [infer R, ...infer Rest]でTが拡張可能ではなくなるのでfalseを返す。

再掲

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