type-challenges をやる
Hello World
Hello, World!
In Type Challenges, we use the type system itself to do the assertion.
For this challenge, you will need to change the following code to make the tests pass (no type check errors).
回答
type HelloWorld = string
解説
なし。
Pick
Implement the built-in Pick<T, K> generic without using it.
Constructs a type by picking the set of properties K from T
For exampleinterface Todo { title: string description: string completed: boolean } type TodoPreview = MyPick<Todo, 'title' | 'completed'> const todo: TodoPreview = { title: 'Clean room', completed: false, }
ヒント
この課題をパスするためには、以下の型を知る必要があります。
解説
keyof
keyof はあるオブジェクトからそのキーを string
または number
のユニオンとして取得します。
例えば、例題にある Todo
インターフェイスに keyof
を適用すると以下の結果が得られます。
type K = keyof Todo // 'title' | 'description' | 'completed'
回答例ではジェネリクスの2つ目の引数は K extends keyof T
であり、つまり MyPick
の2つ目の型引数は1つ目の型引数のキーのみを受け取るよう制約を設けています。
Mapped Types
Mapped Types の基本形は { [P in K]: T }
であり、それぞれの型変数は以下の通りです。
- P パラメーター型
- K 制約型
- T 付与される型
まず [P in K]
という部分はオブジェクトの取りうるキーを反復処理します。in
というキーワードはfor...in
で使われている in
と同じような意味であると考えるとわかりやすいと思います。
このとき K
は string
or number
or symbol
のユニオン型であり、そのユニオンの取りうる数だけオブジェクトのキーが生成されます。
回答例で K
として使われている型変数はジェネリクスの1つ目の型引数のキーのユニオンとなっているのでした。このユニオンで指定した数だけキーが反復処理されるので Pick
の2つ目の型引数で指定キーだけからなるオブジェクトを生成するという目的を達成できていることがわかるかと思います。
T
はそのままオブジェクトのプロパティとして付与される型を表現しています。以下の例を確認してみましょう。
type Foo<T, K extends string> = {
[P in K]: T
}
type Bar = Foo<string, 'a' | 'b' | 'c'>
// type Bar = {
// a: string;
// b: string;
// c: string;
// }
Pick
は元のキーが保有していた値の型を割り当てる必要があるのですが、その型情報はどこから取得すればよいのでしょうか?
今回の回答例では T
の部分は単純な型変数ではなく、T[P]
のようにまるでオブジェクトのプロパティにアクセスするような形の型を使用しています。この型を見てみましょう。
Indexed Access Types
Indexed Access Types
を使うと通常の JavaScript でオブジェクトのプロパティにアクセスするように、オブジェクトの特定の型を取得できます。
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"]; // number
この課題の回答例では T[P]
という形でアクセスされていますが、 P
は2つ目の形引数 K
の取りうる型を反復処理したものですので、これによりそれぞれのキーに対応する適切な型を指定することができます。
for...in
でオブジェクトの個々のプロパティの要素にアクセスする処理を考えてみると、理解しやすいのではないでしょうか?
const todo = {
title: 'my-title',
description: 'lorem ipsum',
completed: false
}
for ( const k in todo) {
console.log(`key: ${k} value: ${todo[k]}`)
}
# console
key: title value: my-title
key: description value: lorem ipsum
key: completed value: false
回答例
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
Readonly
Implement the built-in
Readonly<T>
generic without using it.Constructs a type with all properties of T set to readonly, meaning
the properties of the constructed type cannot be reassigned.For example
interface Todo { title: string description: string } const todo: MyReadonly<Todo> = { title: "Hey", description: "foobar" } todo.title = "Hello" // Error: cannot reassign a readonly property todo.description = "barFoo" // Error: cannot reassign a readonly property
回答例
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
解説
Pick では私達は以下の使い方を学びました。そのことを覚えていれば、簡単に回答できるはずです。
Mapped Types により、型引数で受け取ったすべてのプロパティを反復処理し readonly
修飾子を付与するだけです。
Tuple to Object
Give an array, transform into an object type and the key/value must in the given array.
For example
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const const result: TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
解説
Mapped Types
を使用して反復処理をするのはお決まりのパターンです。ただし、今回は渡される型引数がオブジェクトではなくタプルなので K
に keyof T
のパターンは使用できません。
代わりに Indexed Access Types を利用して T[number]
という型制約を利用します。配列に対して T[number]
とアクセスすると配列から型を取得できます。
const array = ['apple', 'banana', 'strawberry'] as const
type Arr = typeof array // ['apple', 'banana', 'strawberry']
type Fruits = Arr[number] // "apple" | "banana" | "strawberry"
得られた配列の取りうる値をキーとして反復処理を行えばのぞみのオブジェクトを生成することができます。
回答例
type TupleToObject<T extends readonly string[]> = {
[P in T[number]]: P
}
First of Array
Implement a generic
First<T>
that takes an ArrayT
and returns it's first element's type.For example
type arr1 = ['a', 'b', 'c'] type arr2 = [3, 2, 1] type head1 = First<arr1> // expected to be 'a' type head2 = First<arr2> // expected to be 3
ヒント
この課題をパスするためには以下の機能を知る必要があります。
解説
今までの課題で Indexed Access Type
に十分に触れてきたので、配列の先頭の型にアクセスするにはなにをすべきかもうお分かりでしょう。
JavaScript で配列の先頭にアクセスするためには Arr[0]
と添字に 0
を用いればよいのでした。型システム上でもそれは変わりありません。
type First<T extends any[]> = T[0]
しかし、現状の実装では型引数として空の配列を渡された時に対応できません。空の配列は 0
というプロパティを持っていないからですね。
このケースに対応するため配列が空かどうかチェックし空の配列なら never
を返し、そうでないなら配列の先頭の型を返すという条件分岐が必要です。型システム上で条件分岐を実装するには Conditional Types
を使用します。空の配列かどうかの条件部には T extends []
と記述します。
回答例
type First<T extends any[]> = T extends [] ? never : T[0]
Length of Tuple
For given a tuple, you need create a generic
Length
, pick the length of the tupleFor example
type tesla = ['tesla', 'model 3', 'model X', 'model Y'] type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT'] type teslaLength = Length<tesla> // expected 4 type spaceXLength = Length<spaceX> // expected 5
ヒント
この課題をパスするためには以下の機能を知る必要があります。
解説
Indexed Access Types
のことをすでに知っているのであれば、この課題は驚くほど簡単です。普段配列の要素の数を取得するために Array.length
プロパティにアクセスしています。同じように、型変数に対して length
プロパティにアクセスすれば タプルの要素数を取得することができます。
type Length<T extends any> = T['length']
しかし、これだけだと T
が本当に length
プロパティを持っているかどうかわからないので、以下のようなエラーが出力されてしまします。
Type '"length"' cannot be used to index type 'T'.
T
がlength
プロパティを持っていることを伝えるために extends { length: number }
のように制約を持たせることも可能ですが、この指定方法ですとタプルだけでなく string
のような型も引数として渡せてしますので、適切ではありません。これを踏まえた回答例は以下になります。
回答例
type Length<T extends readonly any[]> = T['length']
if
Implement a utils
If
which accepts conditionC
, a truthy return typeT
, and a falsy return typeF
.C
is expected to be eithertrue
orfalse
whileT
andF
can be any type.For example:
type A = If<true, 'a', 'b'> // expected to be 'a' type B = If<false, 'a', 'b'> // expected to be 'b'
ヒント
この課題をパスするためには、以下の機能を知る必要があります。
解説
型引数 C
,T
,F
を受け取り C
が true
なら T
を、C
が false
なら F
を返す If
型を作成します。
まず、問題文から C
は true
もしくは false
である必要があるのでまずはここから埋めてしまいましょう。
type If<C extends boolean, T, F> = any
型システム上で if
のような条件分岐を実装するためには Conditional Types と呼ばれる機能を使います。構文は以下の通りで、三項演算子と同様の演算子なので直感的に理解しやすいと思います。
SomeType extends OtherType ? TrueType : FalseType;
条件部は SomeType
が OtherType
を拡張しているかを定義します。条件を満たす場合には TrueType
を返し、そうでないなら FalseTyep
を返します。上記の構文を課題の例に当てはめると以下の回答例になります。
回答例
type If<C extends boolean, T, F> = C extends true ? T : F
Exclude
mplement the built-in Exclude<T, U>
Exclude from T those types that are assignable to U
ヒント
この課題をパスするためには以下の機能を知る必要があります。
解説
Exclude<T, U> は U
に割り当て可能な型をT
から除外する型です。 Union Types から特定の型を取り除く際に使われます。
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
この課題を解くにあたり重要になるポイントは Conditional Types
が分配法則(Distributive) に従うと
いう点です。Conditional Types
の条件部において T extends U
の T
が ユニオン型である場合 T
に対して反復処理を行い各要素に条件を適用します。
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
そのため、回答例としては T
の各要素を反復し T
が U
を拡張可能であれば never
を返しそうでないなら T
を返すようにすればよいです。
回答例
type MyExclude<T, U> = T extends U ? never : T
Awaited
If we have a type which is wrapped type like Promise. How we can get a type which is inside the wrapped type? For example if we have
Promise<ExampleType>
how to get ExampleType?
ヒント
この課題をパスするためには以下の機能を知る必要があります。
解説
この課題でやるべきは Promise<T>
から T
を取り出すことです。このようにある型から内側の型を取り出すことを Unwrap と呼びます。
はじめのステップとして Promise<string>
から string
を取り出す例を考えてみましょう。これは型引数 T
が Promise<string>
を拡張可能である場合 string
を返すような記述すればよいです。
type Awaited<T extends Promise<any>> = T extends Promise<string> ? string : never
他にも number
や boolean
の例も出してみましょう。
type Awaited<T extends Promise<any>> = T extends Promise<number> ? number : never
type Awaited<T extends Promise<any>> = T extends Promise<boolean> ? boolean : never
この型を特定の型だけでなく一般性を持たせるためには T
が Promise<U>
を拡張可能であるならば U
を返すという記述をすればよさそうです。しかし、U
という型変数はどこから取得すればよいのでしょうか? Promise<any>
という型を受け取ったうえで、実際に条件が評価されるタイミングになったらその具体的な型を代入したいということをしたいのです。
このような場合には infer
キーワードが使えます。infer
は conditional type
のみで使用することができます。infer
は「推論」を意味する単語であり、その型になにかわかった時点で型変数にその値を代入します。
回答例
type Awaited<T extends Promise<any>> = T extends Promise<infer U> ? U : never
Concat
Implement the JavaScript
Array.concat
function in the type system. A type takes the two arguments. The output should be a new array that includes inputs in ltr orderFor example
type Result = Concat<[1], [2]> // expected to be [1, 2]
ヒント
この課題をパスするためには、以下の機能を知る必要があります。
解説
Array.concat と同じことを型システムとして実装します。
ところで、皆さんは普段配列の連結はどのような方法で行っていますか? Array.concat
を使う以外の方法として以下のように スプレット構文 をよく使うと思います。
const arr1 = [1,2,3]
const arr2 = [4,5,6]
const result = [...arr1, ...arr2] // [1, 2, 3, 4, 5, 6]
実は上記の例と同じことが型システムでも可能です。つまりタプル型のなかで ...T
と書くことができるのです。 これが Variadic Tuple Types と呼ばれる機能です。
型システムの中でスプレット構文が使えるとあれば、すでに答えは見えてきているのではないでしょうか?
...T
の書き方ができる型システムは extends any[]
という条件を満たす必要があります。このことを踏まえた回答例は以下の通りです。
回答例
type Concat<T extends any[], U extends any[]> = [...T, ...U]
Includes
Implement the JavaScript
Array.includes
function in the type system. A type takes the two arguments. The output should be a booleantrue
orfalse
.For example
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
ヒント
解説
はじめに、単純の回答例を考えると以下のようになるのではないでしょうか?
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;
U
が T
の配列の要素の型に代入可能であるなら true
をそうでないなら false
を返します。しかし、この回答例ですと以下のケースに対応できません。
回答例
type Includes<T extends readonly any[], U> = T extends [infer L, ...infer R]
? [U, L] extends [L, U]
? true
: Includes<R, U>
: false
これ本当に easy レベルか?
Push
Implement the generic version of
Array.push
For example
type Result = Push<[1, 2], '3'> // [1, 2, '3']
ヒント
この課題をパスするためには、以下の機能を知る必要があります。
解説
Concat と同じ考え方で解くことができます。
JavaScript で Array.push
を使わずに配列の末尾に要素を追加するときはどのようにしていますか?
回答例
type Push<T extends any[], U> = [...T, U]
Unshift
Implement the type version of
Array.unshift
For example
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]
ヒント
この課題をパスするためには、以下の機能を知る必要があります。
解説
単純に Push と逆のことをやればよいですね。
回答例
type Unshift<T extends any[], U> = [U, ...T]
Parameters
Implement the built-in Parameters<T> generic without using it.
ヒント
この課題をパスするためには以下の機能を知る必要があります。
解説
Parameters はある関数型 T
引数の型をタプルとして取得する組み込み型です。
const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: {a: 'A'}): void => {}
const baz = (): void => {}
type T0 = Parameters<typeof foo> // [string, number]
type T1 = Parameters<typeof bar> // [boolean, {a: 'A'}]
type T2 = Parameters<typeof baz> // []
やるべきことは (...args: any[]) => any
という型から (...args: any[])
の部分の具体的な型を取得することです。このように実際に条件が評価されるタイミングになってからその具体的な型を取得するには infer
キーワードを使用します。
回答例
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer U) => any ? U : never
Get Return Type
Implement the built-in
ReturnType<T>
generic without using it.For example
const fn = (v: boolean) => { if (v) return 1 else return 2 } type a = MyReturnType<typeof fn> // should be "1 | 2"
ヒント
この課題をパスするためには以下の機能を知る必要があります。
解説
基本形は Paramerters と同じですね。型を推論するべき箇所が引数から返り値に変わっています。
回答例
type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never
Omit
Implement the built-in
Omit<T, K>
generic without using it.Constructs a type by picking all properties from
T
and then removingK
For example
interface Todo { title: string description: string completed: boolean } type TodoPreview = MyOmit<Todo, 'description' | 'title'> const todo: TodoPreview = { completed: false, }
ヒント
この課題をパスするためには、以下の型を知る必要があります。
解説
やりたいことは Pick
と同じで Mapped Types
を使って新しいオブジェクトを作成すればよいわけですが、第2引数で渡されたキーを除外しなければいけないので、単純な Mapped Type
を使うだけでは回答できません。
とりあえず現時点でわかるところだけを埋めておきましょう。
type MyOmit<T, K extends keyof T> = any
ここでやりたいことは keyof T
を反復した上で反復時の型 P
が P extends K
を満たさないときだけオブジェクトのプロパティに追加することです。
条件分岐が出てきたのでなんとなく Conditional Types
を使えばよいことは想像できますが、どうすれば反復処理の中で条件分岐を使うことができるのでしょうか?
Mapped Types
元のプロパティから新しいプロパティを生成したり、あるプロパティを除外するためには as
句を使用します。
as
句は2通りの使い方があります。1つ目は以下の例の通り template literal types
を用いてプロパティ名をリネームすることができます。
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
type LazyPerson = {
getName: () => string;
getAge: () => number;
getLocation: () => string;
}
2つ目の使い方として、as
句の中で never
を返した場合にはそのプロパティを除外することができます。今回の課題の場合には as
句の中で P
が K
に対して拡張可能であるか検査しそうであるなら never
を返せばよいわけです。
回答例
type MyOmit<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P]
}
Readonly 2
Implement a generic
MyReadonly2<T, K>
which takes two type argumentT
andK
.
K
specify the set of properties ofT
that should set to Readonly. WhenK
is not provided, it should make all properties > readonly just like the normalReadonly<T>
.For example
interface Todo { title: string description: string completed: boolean } const todo: MyReadonly2<Todo, 'title' | 'description'> = { title: "Hey", description: "foobar", completed: false, } todo.title = "Hello" // Error: cannot reassign a readonly property todo.description = "barFoo" // Error: cannot reassign a readonly property todo.completed = true // OK
ヒント
解説
まずは、途中までは Readonly と同じなのでそこまで書いてしまいましょう。
type MyReadonly2<T, K> = {
readonly [P in keyof T]: T[P]
}
通常の Readonly
と異なる点は第2引数で受け取る型のみを readonly
とする点です。 Mapped Types
の反復処理させる集合を K
に変更しましょう。また K
は T
のプロパティ型のみを受け取るように制約を設けます。
type MyReadonly2<T, K extends keyof T> = {
readonly [P in keyof K]: T[P]
}
一方で第2引数で指定されなかった型はどのように表現するのか考えてみましょう。 readonly
を付与しない、ということはなにもしないでそのまま返せばよいのです。
type MyReadonly2<T, K> = T
これで K
で指定されたプロパティと指定されなかったプロパティどちらも表すことができました。最終的にこれらの型を結合して返したいのですから、交差型(Intersection Types) を使いましょう。交差型は同じプロパティ名を持つ時後ろの型が優先されるので順番が重要です。
type MyReadonly2<T, K extends keyof T> = T & {
readonly [P in K]: T[P]
}
しかし、この形ではまだ漏れがあります。 K
が渡されなかった場合にはすべてのプロパティを readonly
にする必要がありますがその要件を満たせていません。
このエラーを解消するために K
に対してデフォルト引数を渡します。これは JavaScriptのデフォルト引数と同様の構文です。デフォルト値として T
のプロパティの取りうるすべての値を渡せばすべてのプロパティに対して反復処理が行われるため要件を満たすことができます。
回答例
type MyReadonly2<T, K extends keyof T = keyof T> = T & {
readonly [P in K]: T[P]
}
こちらの記事ヒントと解説がしっかりされており、とても勉強になります!
素敵な記事をありがとうございます!!
Readonly2で筆者様の回答がテストに引っかかっていたので(ts4.6.4の場合)、僭越ながらご報告させていただきます。
下記が解答を見てまわった感じ一番スッキリしてました。
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P]
} & Omit<T, K>
詳しくはこちらご覧いただけますと幸いです。
報告ありがとうございます👍
この問題の回答当時は ts4.4.4 だったのですが、その後のバージョンアップで挙動が変更になったようですね。
バージョンの記載がなく混乱を招く形になってしまいました🙇♂️
なるほど、バージョンの差による物だったんですね、ありがとうございます!
Deep Readonly
Implement a generic DeepReadonly<T> which make every parameter of an object - and its sub-objects recursively - readonly.
You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on are no need to take into consideration. However, you can still challenge your self by covering different cases as many as possbile.
For example
type X = { x: { a: 1 b: 'hi' } y: 'hey' } type Expected = { readonly x: { readonly a: 1 readonly b: 'hi' } readonly y: 'hey' } const todo: DeepReadonly<X> // should be same as `Expected`
ヒント
解説
途中までは Readonly と同じです。
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P]
}
ここで DeepReadonly
にするための条件を見直してみましょう。この課題ではすべてのパラメーターとそのサブオブジェクトを再帰的に readonly
とする必要があると書いてあります。つまり T[P]
がオブジェクトならさらにサブオブジェクトまで readonly
とし、それ以外ならそのまま T[P]
を返せばよいわけです。「T[P]
がオブジェクトなら〜」という条件が出てきましたので、ここは Conditional Types
の出番です。
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ? /** T[P]がオブジェクトだったときの処理 */ : T[P]
}
T[P]
がオブジェクトかどうかの判定のために組み込み型である Record<Keys, Type>を使用しています。Record<Keys, Type>
はプロパティが Keys
型あり、値が Type
型であるオブジェクト型を生成します。
最後に T[P]
がオブジェクトだったときの処理を埋めましょう。問題文がヒントとなっているように conditional types
おいては再帰的な型を定義することができます。
回答例
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ? DeepReadonly<T[P]> : T[P]
}
Tuple to Union
Implement a generic
TupleToUnion<T>
which covers the values of a tuple to its values union.For example
type Arr = ['1', '2', '3'] const a: TupleToUnion<Arr> // expected to be '1' | '2' | '3'
ヒント
解説
配列型にnumber
でアクセスすると配列の要素の型を取得できます。
const array = ['apple', 'banana', 'strawberry'] as const
type Arr = typeof array // ['apple', 'banana', 'strawberry']
type Fruits = Arr[number] // "apple" | "banana" | "strawberry"
回答例
type TupleToUnion<T extends any[]> = T[number]
Tuple to Object が easy なのになんでこちらは medium なのだろうか・・・
Chainable Options
Last of Array
TypeScript 4.0 is recommended in this challenge
Implement a generic
Last<T>
that takes an ArrayT
and returns it's last element's type.For example
type arr1 = ['a', 'b', 'c'] type arr2 = [3, 2, 1] type tail1 = Last<arr1> // expected to be 'c' type tail2 = Last<arr2> // expected to be 1
ヒント
解説
配列から最後の要素を取り出す方法をいくつか考えてみましょう。真っ先に思いつくのが arr[arr.length - 1]
のように「配列の長さ - 1の添字でアクセスする」という方法ですが、型システム上で四則演算はできません。
type Last<T extends any[]> = T[T['length'] - 1] // ']' expected.
その他の方法を考えてみましょう。単純に配列の先頭の要素から1つずつ取得していって最後に残った要素は配列の最後の要素になります。これを型システム上で表現するには Variadic Tuple Types
を使います。JavaScript では構文エラーになる書き方なので、ちょっと気が付きにくいかもしれないですね。
[...any, L]
この形から L
を取得できればよさそうです。最後の要素の型を推測するためには infer
が使えます、
回答例
type Last<T extends any[]> = T extends [...any, ...infer L] ? L : never
Pop
TypeScript 4.0 is recommended in this challenge
Implement a generic
Pop<T>
that takes an ArrayT
and returns an Array without it's last element.For example
type arr1 = ['a', 'b', 'c', 'd'] type arr2 = [3, 2, 1] type re1 = Pop<arr1> // expected to be ['a', 'b', 'c'] type re2 = Pop<arr2> // expected to be [3, 2]
Extra: Similarly, can you implement
Shift
,Push
andUnshift
as well?
ヒント
解説
Last of Array では配列の最後の要素だけを取得しました。Pop
は配列の最後の要素だけを取り除きます。
回答例
type Pop<T extends any[]> = T extends [...infer P, any] ? P : never
Promise.all
Type the function
PromiseAll
that accepts an array of PromiseLike objects, the returning value should bePromise<T>
whereT
is the resolved result array.const promise1 = Promise.resolve(3); const promise2 = 42; const promise3 = new Promise<string>((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); // expected to be `Promise<[number, number, string]>` const p = Promise.all([promise1, promise2, promise3] as const)
ヒント
解説
PromiseAll
はある配列の型を受け取りそれを Promise
でラップしたものを返します。
はじめのステップとしてまずはそこから記述しましょう。
declare function PromiseAll<T extends any[]>(values: [...T]): Promise<T>
この段階で argument of type 'readonly [1, 2, 3]' is not assignable to parameter of type '[1, 2, 3]'.
というエラーが表示されています。引数の型に readonly
修飾子を付与して修正しましょう。
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<T>
この時点で1つ目のテストケースは成功しますが、残りはエラーとなっています。これは Promise.all
は渡された型の配列に Promise
でラップされている型が含まれている場合それをアンラップする必要があるためです。1つ目のテストケースには Promise
が含まれていないので成功しているわけです。
それではこのエラーを修正しましょう。配列の要素を1つづつ検査し、その要素の型が Promise
であった場合 Awaited でやったように Promise<T>
から T
を取り出せばよいわけです。
回答例
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{
[P in keyof T]: T[P] extends Promise<infer R> ? R : T[P]
}>
TypeLookup
回答例
type TypeLookUp<U, T> = U extends { type: T } ? U : never
TrimLeft
Implement
TrimLeft<T>
which takes an exact string type and returns a new string with the whitespace beginning removed.For example
type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
ヒント
解説
やりたいことは、文字列の先頭がスペースかどうか判定してそうであるなら残りの文字で再帰的に TrimLeft
を呼び出しそうでないなら文字列をそのまま返せばできそうです。
type TrimLeft<S extends string> = /** 先頭文字スペースか? */ ? TrimLeft<L> : S
問題は先頭文字がスペースか判定し、残りも文字列を取得する条件部をどのように記述するかです。対象の型が配列であったのなら [any, ...infer L]
のような形で取得できたのでしょうが今回の対象は文字列なのでそうはいきません。
このような文字列を型として操作したい場合には Template Literal Types
の出番です。
以下のように infer
と組み合わせて使えば「先頭がスペースある文字列」にマッチさせることができます。
type TrimLeft<S> = S extends ` ${infer L}` ? TrimLeft<L> : S;
しかし、この回答だと最後のテストをパスしません。\n
や \t
も取り除く必要があります。
条件部を「先頭文字が
または \n
または \t
」のように OR 条件で判定する必要がありそうです。嬉しいことに、 Template Literal Types
の補完(${}
) にはユニオン型を使うこともできます。
以下の例のように補完にユニオン型が使われた場合にはユニオンによって取りうるすべての文字列のユニオン型として表現されます。
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
課題に戻りましょう。同様に
・ \n
・ \t
のユニオン型を使用すれば先頭文字がいずれかの場合もマッチさせることができます。
回答例
type space = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${space}${infer L}` ? TrimLeft<L> : S
Trim
Implement
Trim<T>
which takes an exact string type and returns a new string with the whitespace from both ends removed.For example
type trimed = Trim<' Hello World '> // expected to be 'Hello World'
ヒント
解説
TrimLeft を拡張して両側からスペースを削除できるようにします。TrimLeft のコードを再掲します。ここから始めましょう。
type space = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${space}${infer L}` ? Trim<L> : S
ここでは初めに左側にスペースがあるか再帰的に検査して取り除きます。これ以上左側にスペースが存在しない状態まで進めたら Conditional Types
の false
句へ入ります。そうしたら今度は右側にスペースがあるパターンでまた同じことをおこなえばよいです。回答例のように Conditional Types
はネストして使用できます。
回答例
type space = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${space}${infer R}` ? Trim<R> : S extends `${infer L}${space}` ? Trim<L> : S
Capitalize
Implement
Capitalize<T>
which converts the first letter of a string to uppercase and leave the rest as-is.For example
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
ヒント
解説
問題の趣旨とは異なるかもしれないですが、回答例として上げときましょう(
intrinsic
キーワードについて以下記事を参考に・・・
回答例
type Capitalize<S extends string> = intrinsic
ビルドインを使わない回答例として以下のような形式がありますね。
type CharMap = { "a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "k": "K", "l": "L", "m": "M", "n": "N", "o": "O", "p": "P", "q": "Q", "r": "R", "s": "S", "t": "T", "u": "U", "v": "V", "w": "W", "x": "X", "y": "Y", "z": "Z" }
type Capitalize<S extends string> =
S extends `${infer First}${infer U}` ?
(First extends keyof CharMap ? `${CharMap[First]}${U}` : S)
: S;
https://github.com/type-challenges/type-challenges/issues/1356[
小文字と大文字の辞書を定義しておいて Template Literal Types
により最初の文字ををキーに辞書から大文字にしたものを取得してから再度文字を結合してるようです。
Replace
Implement
Replace<S, From, To>
which replace the stringFrom
withTo
once in the given stringS
For example
type replaced = Replace<'types are fun!', 'fun', 'awesome'> // expected to be 'types are awesome!'
ヒント
解説
Replace
を実装するには、まずは From
でマッチする文字列をサーチする必要があります。
Template Literal Types
を使えば特定の文字列にマッチさせることは造作もないです。
type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${From}${infer R}`
あとは文字列にマッチしたなら From
を To
にそのまま置き換えるだけでよさそうです。文字列にマッチしなかったら元の文字列をそのまま返します。
type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${From}${infer R}` ? `${L}${To}${R}` : S
しかし、まだ1つのテストに失敗します。どうやら From
に 空文字 ''
が渡されると具合が悪いようです。ここは早期リターンのように From
が 空文字 ''
だった場合には早々に元の文字列を返してしまいましょう。
回答例
type Replace<S extends string, From extends string, To extends string> = From extends ''
? S
: S extends `${infer L}${From}${infer R}` ? `${L}${To}${R}` : S
ReplaceAll
Implement
ReplaceAll<S, From, To>
which replace the all the substringFrom
withTo
in the given stringS
For example
type replaced = ReplaceAll<'t y p e s', ' ', ''> // expected to be 'types'
ヒント
解説
Replace を元に考えてみましょう。Replace
は1度文字列にマッチしたらその場で打ち切っていましたが ReplaceAll
はすべての対象の文字列を置換する必要があります。
勘のいい方ならもうお分かりかもしれないですが、このような場合は再帰が使えます。
回答例
type ReplaceAll<S extends string, From extends string, To extends string> = From extends ''
? S
: S extends `${infer L}${From}${infer R}` ? `${ReplaceAll<L, From, To>}${To}${ReplaceAll<R, From, To>}` : S
Append Argument
For given function type
Fn
, and any typeA
(any in this context means we don't restrict the type, and I don't have in > mind any type 😉) create a generic type which will takeFn
as the first argument,A
as the second, and will produce > function typeG
which will be the same asFn
but with appended argumentA
as a last one.For example,
type Fn = (a: number, b: string) => number type Result = AppendArgument<Fn, boolean> // expected be (a: number, b: string, x: boolean) => number
This question is ported from the original article > by @maciejsikora
回答例
type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer R ? (...args: [...Args, A]) => R : never
Permutation
Length of String
Compute the length of a string literal, which behaves like String#length
.
ヒント
解説
Length of Tuple と似たような課題に見えますが、一筋縄にはいきません。S['length']
は number
を返します。
type LengthOfString<S extends string> = S['length']
LengthOfString<'kumiko'> // number
どうにかして文字数を数える方法はないでしょうか?
考えられる手段として文字列を先頭から1つづつ取り出し再帰的に LengthOfString
を呼び出し再帰が行われた回数を数えることができればよさそうです。
type LengthOfString<S extends string> = S extends `${infer F}${infer L}` ? LengthOfString<L> : S
問題はどのように再帰した回数を数えるかです。 型パラメーターにもう一つ number
型の形変数を加えてみるのはどうでしょう?初めはデフォルト引数として 0
を渡しておき、再帰として LengthOfString
を呼び出すときには引数とで渡された値 + 1して渡すと再帰した回数を数えられそうです。文字列の最後に達して再帰が終了したときには回数をカウントしていた型変数を返します。
type LengthOfString<S extends string, Count extends number = 0> = S extends `${infer F}${infer L}`
? LengthOfString<L, Count + 1>
: Count
良い方法に思えたのですが、これではうまくいきません。型システム上では演算をすることはできないので Count + 1
の部分が不正になります。
他にカウントすることができる方法はないでしょうか?そういえば Length of Tuple
では配列の要素の数だけ T['length']
が値を返すことを知ったのでした。これを使えばうまくいきそうです。つまり、再帰があるたびに配列の要素を1つづつ追加していき、文字列の最後に達したなら T['length']
を返せばよいのです。
回答例
type LengthOfString<S extends string, T extends readonly any[] = []> = S extends `${infer F}${infer L}`
? LengthOfString<L, [...T, F]>
: T['length']
響け!ユーフォニアムだ・・・
/* _____________ Test Cases _____________ */
import { Equal, Expect } from '@type-challenges/utils'
type cases = [
Expect<Equal<LengthOfString<''>, 0>>,
Expect<Equal<LengthOfString<'kumiko'>, 6>>,
Expect<Equal<LengthOfString<'reina'>, 5>>,
Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>,
]
Flatten
In this challenge, you would need to write a type that takes an array and emitted the flatten array type.
For example:
type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]
ヒント
解説
再帰処理を用いて、配列の要素を順に先頭から取り出していき処理を行います。まずはそこから記述しましょう。配列の先頭の要素と残りの要素を取得するには [infer F, ...infer L]
と書けばよいです。
type Flatten<T extends any[]> = T extends [infer F, ...infer L]
? [F, ...Flatten<L>]
: []
再帰処理の終了時には空の配列を返します。配列を平坦化するためには、配列のある要素が配列であった場合、その要素が配列でなくなるまで Flatten
を再帰的に良い出せばよいです。配列の要素が配列かどうかは F extends any[]
で判定します。
回答例
type Flatten<T extends any[]> = T extends [infer F, ...infer L]
? F extends any[] ? [...Flatten<F>, ...Flatten<L>] : [F, ...Flatten<L>]
: []
Append to object
Implement a type that adds a new field to the interface. The type takes the three arguments. The output should be an object with the new field
For example
type Test = { id: '1' } type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
ヒント
解説
オブジェクトにプロパティを追加する方法として真っ先に思いつくのは交差型を使うことでしょうか?
type AppendToObject<T extends Record<string, unknown>, U extends string, V> = T & { [P in U]: V }
しかし、この回答はテストをパスしません。返される型を確認してみると、交差型として返されているいます。この課題では交差型を使わないでプロパティを追加する必要がありそうです。
type Result = AppendToObject<test1, 'home', boolean>
test1 & {
home: boolean;
}
あるオブジェクト型から新しいオブジェクト型を生成するためには Mapped Types
を使いましょう。まず第1引数のオブジェクト型をそのまま返すには以下のように記述します。
type AppendToObject<T extends Record<string, unknown>, U extends string, V> = {
[P in keyof T]: T[P]
}
Mapped Types
はオブジェクト型のプロパティを反復処理して型を生成します。オブジェクト型に新たなプロパティを追加するには反復処理するプロパティに第2引数の U
を追加すればよいでしょう。ユニオン型を使用し Mapped Types
の取りうるプロパティに追加します。
type AppendToObject<T extends Record<string, unknown>, U extends string, V> = {
[P in keyof T | U]: T[P]
}
さらに、ここでは反復処理中の P
が U
型だった場合にはオブジェクトの値の型として V
を渡す必要があります。それ以外の場合なら T
のオブジェクトのプロパティなのでそのまま T[P]
を返します。
回答例
type AppendToObject<T extends Record<string, unknown>, U extends string, V> = {
[P in keyof T | U]: P extends U ? V : T[P]
}
Absolute
Implement the
Absolute
type. A type that take string, number or bigint. The output should be a positive number stringFor example
type Test = -100; type Result = Absolute<Test>; // expected to be "100"
ヒント
解説
まずは、符号の有無は考えず型引数の number
を string
に変換するところを考えてみましょう。これは Template Literal Types
を使えば簡単です。
type Absolute<T extends number | string | bigint> = `${T}`
これで <Absolute<10>, '10'>
や Absolute<9_999n>, '9999'>
などの -
符号のついていないテストケースはパスします。
-
符号を取り除くためには string
に変換した T
を先頭の文字と残りの文字に分解し先頭の文字が -
であるなら残りの文字を返しそれ以外の場合なら string
に変換した T
を返せばよいです。
回答例
type Absolute<T extends number | string | bigint> = `${T}` extends `${infer F}${infer R}`
? F extends '-' ? R : `${T}`
: never
もっと簡潔な回答例もあります。
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer R}` ? R : `${T}`
-
符号を取り除くためにはまず string
に変換した T
が 先頭に -
がついているある文字列にマッチするかどうかを検査します。Template Literal Types
を使えば -${infer R}
という形式で検査をすることができます。
条件に当てはまった場合には -
を除いた残りの文字列である R
を返しそうでないなら string
に変換した T
を返します。
String to Union
Implement the String to Union type. Type take string argument. The output should be a union of input letters
For example
type Test = '123'; type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
ヒント
解説
文字列を先頭から一つづつ取り出して処理します。まずは再帰処理の下地を記述しましょう。
type StringToUnion<T extends string> = T extends `${infer L}${infer R}`
? StringToUnion<R> : /** 文字列を最後まで処理したら最終結果を返す */
文字列を最後まで処理した時にユニオン型を返す必要がありますが、どこかで取得した文字を保持する必要があります。Tuple to Union でタプル型はユニオン型に変換できることはわかっているので、タプルとして文字を保持しておけば良さそうです。
型システム上でなにか保持しておきたいときのパターンとして初めに空の配列をデフォルト引数を渡しておいて、再帰処理で呼び出すたびに要素を追加するという方法が使えます。
回答例
type StringToUnion<T extends string, U extends any[] = []> = T extends `${infer L}${infer R}`
? StringToUnion<R, [...U, L]> : U[number]
Merge
Merge two types into a new type. Keys of the second type overrides keys of the first type.
ヒント
解説
Append to Object と同じ処理を行いましょう。
F
のプロパティと S
のプロパティを反復処理します。
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
[P in keyof F | keyof S]: /** TODO */
};
反復処理の中で P
が T
のプロパティなら F[P]
を そうでないなら S[P]
を返すようにします。
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
[P in keyof F | keyof S]: P extends keyof F ? F[P] : S[P] // Type 'P' cannot be used to index type 'S'.
};
しかしこれではコンパイルが通りません。さらに P
が S
のプロパティであるか検査する必要があります。
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
[P in keyof F | keyof S]: P extends keyof F ? F[P] : P extends keyof S ? S[P] : never
};
これでうまくいっているように見えますが、テストケースは失敗します。どこが悪いのか生成された型を確認してみましょう。
type result = Merge<Foo, Bar>
type result = {
a: number;
b: string;
c: boolean;
}
よく見るとプロパティ b
は Foo
と Bar
どちらにも存在します。プロパティが重複する場合には2つ目の型のプロパティで上書きする必要があるので b
の型は number
でなければいけません。
2つ目の型のプロパティで上書きするようにするには、条件部で先に S
が持つプロパティかどうかを検査する必要があります。
回答例
type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
[P in keyof F | keyof S]: P extends keyof S ? S[P] : P extends keyof F ? F[P]: never
};
CamelCase
for-bar-baz
->forBarBaz
ヒント
解説
回答例
type CamelCase<S extends string> = S extends `${infer L}-${infer R}`
? R extends Capitalize<R> ? `${L}-${CamelCase<R>}`
: `${L}${CamelCase<Capitalize<R>>}`
: S
Diff
Get an
Object
that is the difference betweenO
&O1
ヒント
解説
回答例
type Diff<O, O1> = {
[P in keyof O | keyof O1 as P extends keyof O & keyof O1 ? never : P]: P extends keyof O ? O[P] : P extends keyof O1 ? O1[P] : never
}
AnyOf
Implement Python liked
any
function in the type system. A type takes the Array and returnstrue
if any element of the Array is true. If the Array is empty, returnfalse
.For example:
type Sample1 = AnyOf<[1, "", false, [], {}]>; // expected to be true. type Sample2 = AnyOf<[0, "", false, [], {}]>; // expected to be false.
ヒント
解説
回答例
type Falsy = 0 | false | '' | [] | { [P in any]: never }
type AnyOf<T extends readonly any[]> = T extends [infer L, ...infer R]
? L extends Falsy ? AnyOf<R> : true
: false
isNever
Implement a type IsNever, which takes input type
T
.
If the type of resolves tonever
, returntrue
, otherwisefalse
.For example:
type A = IsNever<never> // expected to be true type B = IsNever<undefined> // expected to be false type C = IsNever<null> // expected to be false type D = IsNever<[]> // expected to be false type E = IsNever<number> // expected to be false
ヒント
解説
中級レベルにしては簡単な課題に思いましたか?思ったよりも一筋縄にはいかない問題です。
type IsNever<T> = T extends never ? true : false
しかし、これではテストに失敗します。IsNever<never>
がなぜだか false
を返してしまいます。
never
型はあらゆる型のサブタイプになることができますが、あらゆる型は never
型のサブタイプになることができません。つまり、never
型には never
型自身のみしか代入できないのです。
それではどのように T
が never
型に代入可能が検査すればよいのでしょうか?
裏ワザのような方法ですが never[]
型は never[]
を拡張することができます。これは Conditional Types
の分配法則に関係します。T extends U ? A : B
の T
がユニオン型の場合 T
のそれぞれの要素に対して条件型が展開されます。しかし、never
は存在しない型として扱われるため T | never
は必ず T
として扱われます。
type T1 = number | never // number
つまりは、Conditional Types
による型の分配が行われると never
は消え去ってしまうのです。
この動作を防ぐためには、Conditional Types
による型の分配をさせないようにしなければなりません。実はConditional Types
による型の分配は型が型変数である場合のみ起こるものです。型変数でなくするためにはなにか別の型で囲めばよいわけですが、配列の []
で囲うのが一番手軽なのでこの方法が使われるわけです。
回答例
type IsNever<T> = [T] extends [never] ? true : false
IsUnion
// TODO
ReplaceKeys
Implement a type ReplaceKeys, that replace keys in union types, if some type has not this key, just skip replacing,
A type takes three arguments.For example:
type NodeA = { type: 'A' name: string flag: number } type NodeB = { type: 'B' id: number flag: number } type NodeC = { type: 'C' name: string flag: number } type Nodes = NodeA | NodeB | NodeC type ReplacedNodes = ReplaceKeys<Nodes, 'name' | 'flag', {name: number, flag: string}> // {type: 'A', name: number, flag: string} | {type: 'B', id: number, flag: string} | {type: 'C', name: number, flag: string} // would replace name from string to number, replace flag from number to string. type ReplacedNotExistKeys = ReplaceKeys<Nodes, 'name', {aa: number}> // {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number} // would replace name to never
ヒント
解説
回答例
type ReplaceKeys<U, T, Y> = {
[P in keyof U]: P extends T
? P extends keyof Y ? Y[P] : never
: U[P]
}
Remove Index Signature
Implement
RemoveIndexSignature<T>
, exclude the index signature from object types.For example:
type Foo = { [key: string]: any; foo(): void; } type A = RemoveIndexSignature<Foo> // expected { foo(): void }
ヒント
解説
オブジェクトのプロパティのある条件に当てはまるときに除外するので Key Remapping in Mapped Types
を使う方向で考えます。プロパティによりフィルタリングするには as
句の中で never
を返せばよいのでした。ベースとなる形から記述しましょう。
type RemoveIndexSignature<T> = {
[P in keyof T as /** プロパティが index signature のとき */ ? never : P]: T[P]
}
オブジェクトプロパティが index signature
のときには除外するため never
を返し、それ以外の場合にはプロパティをそのまま返します。
プロパティが index signature
かどうか判定する処理を考えてみましょう。あるプロパティが通常のプロパティの場合 foo
や bar
のように具体的な型で表現されます。一方で index signature
の場合には string
や number
のように広い型で返されます。
一旦 number
型のことは無視して考えるとつまりある型がリテラル型か文字列型か判定できれば良さそうです。
これを判定するためにはリテラル型は文字列型から拡張できますが、文字列型からリテラル型へは拡張できないという性質を利用します。
string extends 'foo' // false
'foo' extends string // true
string extends string // true
type RemoveIndexSignature<T> = {
[P in keyof T as string extends P ? never : P]: T[P]
}
これで1つ目のテストと3つ目のテストはパスしているはずです。2つ目のテストをパスするためにプロパティが number
型であるパターンを追加しましょう。string extends P
が false
句に入ったときはまだ P
が number
である可能性が残されているのでここに追加します。
回答例
type RemoveIndexSignature<T> = {
[P in keyof T as string extends P ? never : number extends P ? never : P]: T[P]
}
Percentage Parser
Implement PercentageParser<T extends string>.
According to the/^(\+|\-)?(\d*)?(\%)?$/
regularity to match T and get three matches.The structure should be: [
plus or minus
,number
,unit
]
If it is not captured, the default is an empty string.For example:
type PString1 = '' type PString2 = '+85%' type PString3 = '-85%' type PString4 = '85%' type PString5 = '85' type R1 = PercentageParser<PString1> // expected ['', '', ''] type R2 = PercentageParser<PString2> // expected ["+", "85", "%"] type R3 = PercentageParser<PString3> // expected ["-", "85", "%"] type R4 = PercentageParser<PString4> // expected ["", "85", "%"] type R5 = PercentageParser<PString5> // expected ["", "85", ""]
ヒント
解説
文字列をいじくり回すので、お察しの通り Template Literal Types
の出番です。
// TODO
回答例
type PercentageParser<A extends string> = A extends `${infer B}${infer C}${'%'}`
? B extends '+' | '-' ? [B, C, '%'] : ['', `${B}${C}`, '%']
: A extends `${infer B}${infer C}` ? B extends '+' | '-' ? [B, C, ''] : ['', `${B}${C}`, '']
: ['', '', '']
Drop Char
Drop a specified char from a string.
For example:
type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'
ヒント
解説
Replace All とほぼ同じ。マッチした文字を置換する代わりに削除します。
回答例
type DropChar<S extends string, C extends string> = S extends `${infer L}${C}${infer R}`
? `${DropChar<L, C>}${DropChar<R, C>}` : S
MinusOne
Given a number (always positive) as a type. Your type should return the number decreased by one.
For example:
type Zero = MinusOne<1> // 0 type FiftyFour = MinusOne<55> // 54
ヒント
解説
個人的に難易度詐称問題です。型システム上では四則演算は許されないので下記のような回答は不可能です。
type MinusOne<T extends number> = T - 1
ここは Length of String でやったことを思い出しましょう。型システムで数を数えるには再帰とタプルを使うのがパターンです。
タプルの length
が T
に代入可能 = 同じ数字になるまでタプルに要素を追加していき、最後にタプルの先頭以外の要素を取り出せば計算できます。
type MinusOne<T extends number, U extends readonly any[] = []> = T extends U['length']
? U extends [any, ...infer R] ? R['length'] : never
: MinusOne<T, [...,U any]>
一見良さそうな回答なのですが、いくつかのテストケースに失敗してしまいます。実は TypeScript の再帰は50回が上限です。そのため、50を超える数を型変数 T
に渡しているケースでは再帰条件に達してしまい予期する結果が得られないわけです。ですので、なんとかして再帰上限を突破しなければなりません。
再帰上限を突破するには、再帰の再帰を利用します。再帰上限の50は一つのスタックに対して制限ですので、再帰上限を達する前にさらに再帰でスタックをもう一つ増やせばよいわけです。
詳しくは以下の記事を参照してください。
今回の例では数字の桁ごとに再帰処理を行うようにします。例えば渡された型変数の値が 56
の場合には初めに 6
を取り出して6つの要素を持つタプルを作成してから次に 5
を取り出して5つの要素を持つタプルを作成します。
数字を桁ごとに取り出すためには文字列に変換した後に Template Literal Types
を使用して順に取り出します。
type GenerateTuple<T extends string, U extends readonly = any[] = []> = `T extends ${infer L}${infer R}`
? GenerateTuple<R, /** Lの要素の数を持つタプルを作る処理 */ > : U
type MinusOne<T extends number> = GenerateTuple<`${T}`> extends [any, ...infer R] ? R['length'] : 0
作成した2つのタプルをマージする際に5の要素を持つタプルは十の位の数でありますから、これを50の要素を持つ配列に変換しておく必要があります。
この変換をするには、以下のように10回同じ配列を展開すればよいわけです。
type Arr10<T extends readonly any[]> = [...T,...T,...T,...T,...T,...T,...T,...T,...T,...T]
もともとあったある数字からその数だけ要素を持つタプルを作成する処理を type
として作成しておきましょう。
type DigitToArr<T extends number, U extends readonly any[] = []> = T extends U['length'] ? U : DigitToArr<T, [...U, any]>
ただし、今回の変更で文字列化した数字を受け取る必要があります。U['length]
に拡張可能化調べるのに今度は文字列から数値に変換する必要があります。そのために 0
〜 9
の要素を持つタプル型を作成しておきます。LenArr['1']
は 1
を返すので、これを使えば数値方に変化できるわけです。
type LenArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type StrDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type DigitToArr<T extends StrDigit, U extends readonly any[] = []> = LenArr[T] extends U['length'] ? U : DigitToArr<T, [...U, any]>
作成した DigitToArr
を GenerateTuple
の中で使用しましょう。DigitToArr
の型引数は StrDigit
型である必要があるのでこれを検査する必要があります。
type GenerateTuple<T extends string, U extends readonly any[] = []> = T extends `${infer L}${infer R}`
? L extends StrDigit ? GenerateTuple<R, [...Arr10<U>, ...DigitToArr<L>]> : never
: U
回答例
type LenArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type StrDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type Arr10<T extends readonly any[]> = [...T,...T,...T,...T,...T,...T,...T,...T,...T,...T]
type DigitToArr<T extends StrDigit, U extends readonly any[] = []> = LenArr[T] extends U['length'] ? U : DigitToArr<T, [...U, any]>
type GenerateTuple<T extends string, U extends readonly any[] = []> = T extends `${infer L}${infer R}`
? L extends StrDigit ? GenerateTuple<R, [...Arr10<U>, ...DigitToArr<L>]> : never
: U
type MinusOne<T extends number> = GenerateTuple<`${T}`> extends [any, ...infer R] ? R['length'] : 0
PickByUnion
From
T
, pick a set of properties whose type are assignable toU
.For Example
type OnlyBoolean = PickByType<{ name: string count: number isReadonly: boolean isEnable: boolean }, boolean> // { isReadonly: boolean; isEnable: boolean; }
ヒント
解説
オブジェクトから特定の条件のプロパティを取り出すときたら Key Remapping in Mapped Types
の出番です。T[P]
が U
に代入可能であればそのまま P
を返しそうでないなら never
を返してプロパティを除外します。
回答例
type PickByType<T, U> = {
[P in keyof T as T[P] extends U ? P: never]: T[P]
}
StartsWith
Implement
StartsWith<T, U>
which takes two exact string types and returns whetherT
starts withU
For example
type a = StartsWith<'abc', 'ac'> // expected to be false type b = StartsWith<'abc', 'ab'> // expected to be true type c = StartsWith<'abc', 'abcd'> // expected to be false
ヒント
解説
逆詐称気味。Template Literal Types
を使えばある文字列が特定の文字にマッチするか判定するのは容易です。
回答例
type StartsWith<T extends string, U extends string> = T extends `${U}${any}` ? true : false
EndWith
Implement
EndsWith<T, U>
which takes two exact string types and returns whetherT
ends withU
ヒント
解説
StartsWith
とほぼ同じ。
回答例
type EndsWith<T extends string, U extends string> = T extends `${any}${U}` ? true : false
PartialByKeys
Implement a generic
PartialByKeys<T, K>
which takes two type argumentT
> andK
.
K
specify the set of properties ofT
that should set to be optional. > WhenK
is not provided, it should make all properties optional just like > the normalPartial<T>
.For example
interface User { name: string age: number address: string } type UserPartialName = PartialByKeys<User, 'name'> // { name?:string; > age:number; address:string }
ヒント
解説
一瞬 Key Remapping in Mapped Types
が有効に働きそうに思えますが、条件によってプロパティを取り除くことはできても readonly
や ?
などの修飾子を付与するかどうかの判定は行えません。
回答の考え方は以下のとおりです。
-
K
で指定したプロパティのみを反復処理してすべてオプショナルを付与したオブジェクト型を作成する -
T
からK
を取り除いたオブジェクト型を作成してマージする
type PartialByKeys<T, K extends PropertyKey = keyof T> = {
[P in keyof T]?: T[P]
} & Omit<T, K>
この状態ですとインターセクション型として表現されてしまうため、インターセクション型をフラットなオブジェクトに変換する型を定義してこれを利用します。
type IntersectionToObj<T> = {
[P in keyof T]: T[P]
}
回答例
type IntersectionToObj<T> = {
[P in keyof T]: T[P]
}
type PartialByKeys<T, K extends PropertyKey = keyof T> = IntersectionToObj<{
[P in keyof T]?: T[P]
} & Omit<T, K>
>
RequiredByKeys
Implement a generic
RequiredByKeys<T, K>
which takes two type argumentT
andK
.
K
specify the set of properties ofT
that should set to be required. WhenK
is not provided, it should make all properties required just like the normalRequired<T>
.For example
interface User { name?: string age?: number address?: string } type UserPartialName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
ヒント
解説
PartialByKeysとほぼ同じ。オプショナル修飾子(?
)の前に -
を付与するとその修飾子を取り除けます。
回答例
type IntersectionToObj<T> = {
[P in keyof T]: T[P]
}
type RequiredByKeys<T, K extends PropertyKey = keyof T> = IntersectionToObj<{
[P in keyof T]-?: T[P]
} & Omit<T, K>
>
Mutable
Implement the generic
Mutable<T>
which makes all properties inT
mutable (not readonly).For example
interface Todo { readonly title: string readonly description: string readonly completed: boolean } type MutableTodo = Mutable<T> // { title: string; description: string; >completed: boolean; }
ヒント
解説
readonly
や ?
などの修飾子に -
を付与するとその修飾子を取り除くことができます。
回答例
type Mutable<T> = {
-readonly[P in keyof T]: T[P]
}
OmitByType
From
T
, pick a set of properties whose type are not assignable toU
.For Example
type OmitBoolean = OmitByType<{ name: string count: number isReadonly: boolean isEnable: boolean }, boolean> // { name: string; count: number }
解説
PickByType
とほぼ同じ
回答例
type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P]
}
Object Entries
type ObjectEntries<T, U extends keyof T = keyof T> = U extends infer R ? [R, Exclude<T[U], undefined>] : never
Shift
ヒント
回答例
type Shift<T extends readonly any[]> = T extends [any, ...infer R] ? R : never
Tuple to Nested Object
Given a tuple type
T
that only contains string type, and a typeU
, build an object recursively.type a = TupleToNestedObject<['a'], string> // {a: string} type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}} type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type
回答例
type TupleToNestedObject<T extends readonly any[], U> = T extends [infer L, ...infer R]
? L extends string
? { [P in L]: TupleToNestedObject<R, U> }
: never
: U
Reverse
Implement the type version of
Array.reverse
For example:
type a = Reverse<['a', 'b']> // ['b', 'a'] type b = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a']
回答例
type Reverse<T extends readonly any[], U extends readonly any[] = []> = T extends [...infer L, infer R]
? Reverse<L, [...U, R]>
: U
FlipArguments
Implement the type version of lodash's
_.flip
.Type
FlipArguments<T>
requires function typeT
and returns a new function type which has the same return type of T but reversed parameters.For example:
type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void> // (arg0: boolean, arg1: number, arg2: string) => void
回答例
type FlipArguments<T, U extends readonly any[] = []> = T extends (...args: infer Args) => infer R
? Args extends [...infer F, infer L]
? FlipArguments<(...args: F) => R, [...U, L]>
: (...args: U) => R
: never
Flatten Depth
Recursively flatten array up to depth times.
For example:
type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2> // [1, 2, 3, 4, [5]]. flattern 2 times type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
If the depth is provided, it's guaranteed to be positive integer.
ヒント
解説
Flatten を拡張して平坦化の回数に制限を加えたものです。Flatten の回答を前提に始めましょう。
type Flatten<T extends any[]> = T extends [infer F, ...infer L]
? F extends any[] ? [...Flatten<F>, ...Flatten<L>] : [F, ...Flatten<L>]
: []
追加の要素として第2引数に平坦化する回数を受け取ります。デフォルトは 1
です。そして再帰のたびに平坦化した回数をカウントして 第2引数の値と一致したときには再帰を終了するようにします。数を数えるにはお決まりのパターンとしてデフォルトが空のタプルを渡します。
type Flatten<T extends any[], N extends number = 1, A extends readonly any[] = []> = T extends [infer F, ...infer L]
? F extends any[] ? [...Flatten<F>, ...Flatten<L>] : [F, ...Flatten<L>]
: []
後は再帰のたびにタプルの要素を増やしていく処理を追加しておけばよいです。注意点として再帰の全体の数の制限ではなく、ある配列出会ったときその深さに対して制限を適用するので先頭以外の要素に対して再帰呼び出しをするときにはタプルの要素を増やしません。
回答例
type FlattenDepth<T extends any[], N extends number = 1, A extends readonly any[] = []> = A['length'] extends N ? T
: T extends [infer F, ...infer L]
? F extends any[] ? [...FlattenDepth<F, N, [any, ...A]>, ...FlattenDepth<L, N>] : [F, ...FlattenDepth<L, N, A>]
: []
Flip
Implement the type of
just-flip-object
. Examples:Flip<{ a: "x", b: "y", c: "z" }>; // {x: 'a', y: 'b', z: 'c'} Flip<{ a: 1, b: 2, c: 3 }>; // {1: 'a', 2: 'b', 3: 'c'} flip<{ a: false, b: true }>; // {false: 'a', true: 'b'}
No need to support nested objects and values which cannot be object keys such as arrays
ヒント
- Mapped Types
- Conditional Types
- Indexed Access Types
- Key Remapping in Mapped Types
- Template Literal Types
解説
オブジェクトを変換するときには Mapped Types
の出番です。
プロパティも変更する必要があるので Key Remapping in Mapped Types
を使用して プロパティにもともと値であったものを割り当てます。
type Flip<T> = {
[P in keyof T as T[P]]: P
}
この段階では 3 つ目のテストに失敗してしまいます。オブジェクトのプロパティには string
number
symbol
しか使えないのですが boolean
を割り当てようとしているためです。
回答例としてプロパティとして割り当てようとしている値が string
number
symbol
ではなかったときには Template Literal Types
を使い無理やり string
に変換してやればよいです。
回答例
type Flip<T> = {
[P in keyof T as T[P] extends PropertyKey ? T[P] : `${T[P]}`]: P
}
Greater Than
In This Challenge, You should implement a type
GreaterThan<T, U>
likeT > U
Negative numbers do not need to be considered.
For example
GreaterThan<2, 1> //should be true GreaterThan<1, 1> //should be false
Good Luck!
解説
accumulator として いつものタプルを型引数に追加します。
type GreaterThan<T extends number, U extends number, A extends any[] = []>
再帰の度にタプルの要素を一つづつ増やしていき先に T
が タプルの要素と同じとなったら T
のほうが小さいと判定でき、先に U
がタプルの要素と同じになるなら U
のほうが小さいと判定できます。
回答例
type GreaterThan<T extends number, U extends number, A extends any[] = []> = A['length'] extends T
? false
: A['length'] extends U
? true
: GreaterThan<T, U, [any, ...A]>;
BEM style string
The Block, Element, Modifier methodology (BEM) is a popular naming convention for classes in CSS.
For example, the block component would be represented as
btn
, element that depends upon the block would be represented asbtn__price
, modifier that changes the style of the block would be represented asbtn--big
orbtn__price--warning
.Implement
BEM<B, E, M>
which generate string union from these three parameters. WhereB
is a string literal,E
andM
are string arrays (can be empty).
ヒント
解説
文字列を操作するので Template Literal Types
の出番です。深く考えずに B__E--M
のように結合してあげればよいわけです。
ここでは E
と M
は string
ではなく string[]
型として渡されることとなっているので素直に結合はできません。E
と M
は ユニオン型とに変換されることが期待されているので Tupple To Union でやったように E[number]
として変換します
type BEM<B extends string, E extends string[], M extends string[]> = `${B}__${E[number]}--${M[number]}`
この段階ではいくつかのテストに失敗します。E
と M
は空の配列として渡される可能性があるのでその場合には結果から除外する必要があります。
空の配列の判定は E['length'] extends 0
のように配列の長さが 0
であれば調べればよいです。
回答例
type BEM<B extends string, E extends string[], M extends string[]> = E['length'] extends 0 ? `${B}--${M[number]}`
: M['length'] extends 0 ? `${B}__${E[number]}`
: `${B}__${E[number]}--${M[number]}`
Zip
In This Challenge, You should implement a type
Zip<T, U>
, T and U must beTuple
type exp = Zip<[1, 2], [true, false]> // expected to be [[1, true], [2, false]]
ヒント
解説
この課題でやりたいのは以下のようなことです。
type Zip<T extends readonly any[], U extends readonly any[]> = [[T[0], U[0]], [T[1], U[1]]
for
ループのように配列の要素を一つづつ処理したい場合には Variadic Tuple Types
と infer
で先頭の要素と残りの要素に分けて再帰を使用します。
回答例
type Zip<T extends readonly any[], U extends readonly any[], A extends readonly any[] = []> =
T extends [infer RF, ...infer RL] ?
U extends [infer UF, ...infer UL] ?
Zip<RL, UL, [...A, [RF, UF]]>
: A : A
TrimRight
Implement
TrimRight<T>
which takes an exact string type and returns a new string with the whitespace ending removed.For example:
type Trimed = TrimRight<' Hello World '> // expected to be ' Hello World'
ヒント
解説
TrimLeft と同じ
回答例
type space = ' ' | '\n' | '\t'
type TrimRight<S extends string> = S extends `${infer L}${space}` ? TrimRight<L> : S
IsTuple
Implement a type
IsTuple
, which takes an input typeT
and returns whetherT
is tuple type.For example:
type case1 = IsTuple<[number]> // true type case2 = IsTuple<readonly [number]> // true type case3 = IsTuple<number[]> // false
解説
まずはタプルを満たす条件についておさらいしておきましょう。
- readonly である
- 配列を拡張した型である
- 配列の長さは固定である
ひとまず上記2つを満たしているかどうか調べることでいくつかのテストはパスします。
type IsTuple<T> = T extends readonly any[] ? true : false
しかし、この状態以下のテストのみ失敗してしまいます。
Expect<Equal<IsTuple<number[]>, false>>,
number[]
は タプル型ではありませんが 1. と 2. の条件を満たします。3. の配列の長さの条件を調べることによってこの形は除外することができます。
通常の配列型の場合には長さが未確定のため T['length']
にアクセスした場合には number
型を返しますが、タプル型の場合に T['length']
にアクセスした場合にはより具体的な型('2', '3' など)を返します。
つまり number
が T['length']
を拡張可能か追加で調べればよいわけです。
回答例
type IsTuple<T> = T extends readonly any[] ?
number extends T['length'] ? false : true : false
Fill
Fill
, a common JavaScript function, now let us implement it with types.
Fill<T, N, Start?, End?>
, as you can see,Fill
accepts four types of parameters, of whichT
andN
are required parameters, andStart
andEnd
are optional parameters.
The requirements for these parameters are:T
must be atuple
,N
can be any type of value,Start
andEnd
must be integers greater than or equal to 0.type exp = Fill<[1, 2, 3], 0> // expected to be [0, 0, 0]
In order to simulate the real function, the test may contain some boundary conditions, I hope you can enjoy it :)
ヒント
解説
配列の要素を1つづつ取り出してまずはいつもの infer
で先頭の要素だけ取り出します。ついでに再帰処理御用達の accumulator も型引数に追加
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?
配列の要素を N
で置き換えるのは現在の配列のインデックスが Start
以上かつ End
以下の場合のみなのでその条件を追加します。
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?
A['length'] extends Start ?
Start extends End ?
現在の配列のインデックスが End
以上の場合にはこれ以上置き換える必要がないことがわかっているので accumulator と現在の配列の要素を返して終了します。
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?
A['length'] extends Start ?
Start extends End ?
[...A, ...T]
Start
以上 End
以下の時には accumulator に 置き換え対象の N
を渡して再帰します。この時 次の再帰で A['length'] extends Start
を満たすようにするために Start
の値をインクリメントします。
Start
以下の場合には accumulator に現在の要素を追加して再帰をします。
回答例
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?
A['length'] extends Start ?
Start extends End ?
[...A, ...T]
: Fill<R, N, [...A, any]['length'] & number, End, [...A, N]>
: Fill<R, N, Start, End, [...A, L]>
: A
Without
Implement the type version of Lodash.without, Without<T, U> takes an Array T, number or array U and returns an Array without the elements of U.
type Res = Without<[1, 2], 1>; // excepted to be [2] type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // excepted to be [4, 5] type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // excepted to be []
ヒント
回答例
いつものように再帰で配列の要素を一つづつ取り出して処理を行います。
やることは簡単で再帰処理の中で要素が U
を拡張可能であるなら accumulator に追加せず、そうでないなら accumulator に追加すれば U
の要素を取り除いた配列が出来上がります。
type Without<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R]
? L extends U ? Without<R, U, A> : Without<R, U, [...A, L]>
: A
ただ一つ厄介なのは U
は number
か number[]
どちらかも受け付ける点です。U
が number[]
型で来る場合には一度ユニオン型に変換してから判定を行う必要があります。
タプルをユニオン型に変換するには U[number]
です。
回答例
type Without<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R]
? U extends any[]
? L extends U[number] ? Without<R, U, A> : Without<R, U, [...A, L]>
: L extends U ? Without<R, U, A> : Without<R, U, [...A, L]>
: A
Trunk
Implement the type version of
Math.trunc
, which takes string or number and returns the integer part of a number by removing any fractional digits.For example:
type A = Trunc<12.34> // 12
ヒント
解説
期待結果が string
なことがもはやヒントのようなものですが、Template Literal Types
を使います。
小数点 .
で前後を切り分けて .
より前の部分を取り出せばよいです。
回答例
type Trunc<T extends string | number> = `${T}` extends `${infer A}.${infer B}` ? A : `${T}`
IndexOf
Implement the type version of Array.indexOf, indexOf<T, U> takes an Array T, any U and returns the index of the first U in Array T.
type Res = IndexOf<[1, 2, 3], 2>; // excepted to be 1 type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; // excepted to be 2 type Res2 = IndexOf<[0, 0, 0], 2>; // excepted to be -1
ヒント
解説
いつもの再帰セット。
type IndexOf<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R]
L
が U
を拡張可能ならその時点での accumulator の要素数を返します。
要素が見つからなかったときには -1
を返します。
回答例
type IndexOf<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R]
? L extends U ? A['length'] : IndexOf<R, U, [...A, any]>
: -1
Join
Implement the type version of Array.join, Join<T, U> takes an Array T, string or number U and returns the Array T with U stitching up.
type Res = Join<["a", "p", "p", "l", "e"], "-">; // expected to be 'a-p-p-l-e' type Res1 = Join<["Hello", "World"], " ">; // expected to be 'Hello World' type Res2 = Join<["2", "2", "2"], 1>; // expected to be '21212' type Res3 = Join<["o"], "u">; // expected to be 'o'
回答例
type Join<T extends readonly any[], U, A extends string = ''> = T extends [infer L, ...infer R]
? A extends ''
? Join<R, U, `${L}`>
: Join<R, U, `${A}${U}${L}`>
: A
LastIndexOf
Implement the type version of
Array.lastIndexOf
,LastIndexOf<T, U>
takes an ArrayT
, anyU
and returns the index of the lastU
in ArrayT
For example:
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3 type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
回答例
type LastIndexOf<T extends any[], U, A extends any[] = [], C extends any[] = []> = T extends [infer L, ...infer R]
? L extends U ? LastIndexOf<R, U, [...A, any], A> : LastIndexOf<R, U, [...A, any], C>
: C['length'] extends 0 ? -1 : C['length']
Unique
Implement the type version of Lodash.uniq, Unique<T> takes an Array T, returns the Array T without repeated values.
type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3] type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7] type Res2 = Unique<[1, "a", 2, "b", 2, "a"]>; // expected to be [1, "a", 2, "b"]
ヒント
解説
いつもの再帰セットで配列をループし accumulator にすでに追加済みであれば何もせずに、そうでないなら accumulator に追加します。
accumulator に追加済みかどうかは accumulator をユニオン型に変換して判定を行います。
回答例
type Unique<T extends any[], A extends any[] = []> = T extends [infer L, ... infer R]
? L extends A[number] ? Unique<R, A> : Unique<R, [...A, L]>
: A
Simple Vue
Implement a simpiled version of a Vue-like typing support.
By providing a function name
SimpleVue
(similar toVue.extend
ordefineComponent
), it should properly infer thethis
type inside computed and methods.In this challenge, we assume that SimpleVue take an Object with
data
,computed
andmethods
fields as it's only argument,
data
is a simple function that returns an object that exposes the contextthis
, but you won't be accessible to other computed values or methods.
computed
is an Object of functions that take the context asthis
, doing some calculation and returns the result. The computed results should be exposed to the context as the plain return values instead of functions.
methods
is an Object of functions that take the context asthis
as well. Methods can access the fields exposed bydata
,computed
as well as othermethods
. The different betweencomputed
is thatmethods
exposed as functions as-is.The type of
SimpleVue
's return value can be arbitrary.const instance = SimpleVue({ data() { return { firstname: 'Type', lastname: 'Challenges', amount: 10, } }, computed: { fullname() { return this.firstname + ' ' + this.lastname } }, methods: { hi() { alert(this.fullname.toLowerCase()) } } })
ヒント
解説
data
,computed
,methods
のみのもつ簡易的な Vue ライクな型を作成します。
以下の目的を達成する必要があります。
-
computed
内のthis
からはdata
,computed
にアクセスできる -
methods
内のthis
からはdata
,computed
,methods
にアクセスできる -
computed
はメソッドではなくオブジェクトのプロパティとしてアクセスできる -
data
内のthis
からは他のプロパティにアクセスできない
まずやるべきは、data
,computed
,methdos
のそれぞれの型を取得することです。これらは動的に変化するので、ジェネリクスによる型変数を利用して型を取得できます。
declare function SimpleVue<D, C, M>(options: {
data: () => D,
computed: C
methods: M
}): any
続いて this
に型を付けていきましょう。プロパティが data
のような関数の場合には関数の1番目の引数を this
にすることで、this
の型を指定できます。data
の this
からは他のどのプロパティについてもアクセスできないので空のオブジェクト {}
を this
として渡します。
declare function SimpleVue<D, C, M>(options: {
data: (this: {}) => D,
computed: C
methods: M
}): any
computed
,methods
のようなオブジェクトのプロパティに対して this
の型を付けるために ThisType と呼ばれる Utility Type を使います。
declare function SimpleVue<D, C, M>(options: {
data: (this: {}) => D,
computed: C & ThisType<D & C>
methods: M & ThisType<D & M & C>
}): any
ここまででほとんどのエラーは取り除かれましたが methods
内で computed
プロパティにアクセスするときに適切な型が得られていません。
computed
の元々の型自体はメソッドとして定義していますが、this
から computed
にアクセスするときには通常のオブジェクトのプロパティとしてアクセスできる必要があるためです。
そのため C
はプロパティはそのままに値の型をを関数の返り値の型に変換する必要があります。
関数の返り値の型を得るためには ReturnType
を使用します。
回答例
type GetComputed<C> = C extends Record<string, (...args: any[]) => any>
? { [S in keyof C]: ReturnType<C[S]> }
: never
declare function SimpleVue<D, C, M>(options: {
data: (this: {}) => D,
computed: C & ThisType<D & C>
methods: M & ThisType<D & M & GetComputed<C>>
}): any
Currying 1
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
For example:
const add = (a: number, b: number) => a + b const three = add(1, 2) const curriedAdd = Currying(add) const five = curriedAdd(2)(3)
The function passed to Currying
may have multiple arguments, you need to correctly type it.
In this challenge, the curried function only accept one argument at a time. Once all the argument is assigned, it should return its result.
type Curry<T, U> = T extends [infer L, ...infer R]
? (a: L) => C<R, U> : U
declare function Currying<F>(fn: F): F extends (...args: infer T) => infer U ? Curry<T,U>: never