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 ArrayTand 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
Ifwhich accepts conditionC, a truthy return typeT, and a falsy return typeF.Cis expected to be eithertrueorfalsewhileTandFcan 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.concatfunction 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.includesfunction in the type system. A type takes the two arguments. The output should be a booleantrueorfalse.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.pushFor 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.unshiftFor 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
Tand then removingKFor 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 argumentTandK.
Kspecify the set of properties ofTthat should set to Readonly. WhenKis 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 ArrayTand 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 ArrayTand 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,PushandUnshiftas well?
ヒント
解説
Last of Array では配列の最後の要素だけを取得しました。Pop は配列の最後の要素だけを取り除きます。
回答例
type Pop<T extends any[]> = T extends [...infer P, any] ? P : never
Promise.all
Type the function
PromiseAllthat accepts an array of PromiseLike objects, the returning value should bePromise<T>whereTis 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 stringFromwithToonce in the given stringSFor 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 substringFromwithToin the given stringSFor 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 takeFnas the first argument,Aas the second, and will produce > function typeGwhich will be the same asFnbut with appended argumentAas 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) => numberThis 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
Absolutetype. 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
Objectthat 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
anyfunction in the type system. A type takes the Array and returnstrueif 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 whetherTstarts withUFor 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 whetherTends 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.
Kspecify the set of properties ofTthat should set to be optional. > WhenKis 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 argumentTandK.
Kspecify the set of properties ofTthat should set to be required. WhenKis 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 inTmutable (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
Tthat 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.reverseFor 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 typeTand 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 1If 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 > UNegative numbers do not need to be considered.
For example
GreaterThan<2, 1> //should be true GreaterThan<1, 1> //should be falseGood 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--bigorbtn__price--warning.Implement
BEM<B, E, M>which generate string union from these three parameters. WhereBis a string literal,EandMare 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 beTupletype 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 typeTand returns whetherTis 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,Fillaccepts four types of parameters, of whichTandNare required parameters, andStartandEndare optional parameters.
The requirements for these parameters are:Tmust be atuple,Ncan be any type of value,StartandEndmust 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, anyUand returns the index of the lastUin ArrayTFor 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.extendordefineComponent), it should properly infer thethistype inside computed and methods.In this challenge, we assume that SimpleVue take an Object with
data,computedandmethodsfields as it's only argument,
datais a simple function that returns an object that exposes the contextthis, but you won't be accessible to other computed values or methods.
computedis 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.
methodsis an Object of functions that take the context asthisas well. Methods can access the fields exposed bydata,computedas well as othermethods. The different betweencomputedis thatmethodsexposed 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>>
}): anyCurrying 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