TypeScript Handbook を読む (6. Generics)

[TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/basic-types.html) を読み進めていく第六回目。

1. [Basic Types](http://qiita.com/murank/items/9c1f633545842c953947)
1. [Variable Declarations](http://qiita.com/murank/items/913f1c1e4e725047436c)
1. [Interfaces](http://qiita.com/murank/items/553276c3dc2eec9bfe47)
1. [Classes](http://qiita.com/murank/items/7e8f15225bc8259e9bf3)
1. [Functions](http://qiita.com/murank/items/e310ce68ed5a0058d42d)
1. Generics (今ココ)
1. [Enums](http://qiita.com/murank/items/cc487562b21bae3728ba)
1. [Type Inference](http://qiita.com/murank/items/e67ad7426020321228e9)
1. [Type Compatibility](http://qiita.com/murank/items/606efb0615294a6826e3)
1. [Advanced Types](http://qiita.com/murank/items/bc1776efbecbd2120d94)
1. [Symbols](http://qiita.com/murank/items/04b3b31caa677750f240)
1. [Iterators and Generators](http://qiita.com/murank/items/7ceec53dde886f31a682)
1. [Modules](http://qiita.com/murank/items/0fc4e059be8ea5e1c274)
1. [Namespaces](http://qiita.com/murank/items/0dfad4a0bb91aaa6c97b)
1. [Namespaces and Modules](http://qiita.com/murank/items/0be1978a3515fc76e7ba)
1. [Module Resolution](http://qiita.com/murank/items/5f11466474d49498bbd5)
1. [Declaration Merging](http://qiita.com/murank/items/ec90ba04edab1ea3718e)
1. [JSX](http://qiita.com/murank/items/90164aa9a3bed3c49a80)
1. [Decorators](https://qiita.com/murank/items/580e34883e0832ca7aed)
1. [Mixins](https://qiita.com/murank/items/e77ad8359ed1fd82822d)
1. [Triple-Slash Directives](https://qiita.com/murank/items/faff57883338ebdeda12)
1. [Type Checking JavaScript Files](https://qiita.com/murank/items/588eaddfe8af59914658)


# Generics
[原文](https://www.typescriptlang.org/docs/handbook/generics.html)

## Hello World of Generics
ジェネリクスの入門として、渡された引数をそのまま返す、恒等関数を実装してみましょう。

まず、ジェネリクスを使用せずに実装しようとすると、以下のように特定の型に対して実装するか、

```ts:TypeScript
function identity(arg: number): number {
    return arg;
}
```

`any` を使用することになるでしょう。

```ts:TypeScript
function identity(arg: any): any {
    return arg;
}
```

`any` を使用するとどんな型でも受け付けることができますが、戻り値として引数をそのまま返却した場合に型情報を失ってしまいます。

代わりに *型変数* を使用することで型情報を保持することができます。
以下の例では型変数 `T` を用いて引数の型を保持し、戻り値の型として設定しています。

```ts:TypeScript
function identity<T>(arg: T): T {
    return arg;
}
```
```js:JavaScript
function identity(arg) {
    return arg;
}
```
>インタフェースと同じく、ジェネリクスはあくまでもコンパイラのチェックに使用されるだけで、コンパイル後のコードには影響しないのね

ジェネリック関数を使用するには、型変数に渡す型を `<>` で囲んで指定する方法と、型推論を使用する方法の 2 通りがあります。

```ts:TypeScript
let output = identity<string>("myString");  // output の型は 'string'
```
```ts:TypeScript
let output = identity("myString");  // output の型は 'string'
```

## Working with Generic Type Variables
先ほどの例の中で、型変数を指定した引数が`any` 型として扱われていることに気づいたかもしれません。

そのため、以下のコードはエラーになります。

```ts:TypeScript
function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // エラー。T は 'length' を持つとは限らない
    return arg;
}
```

このコードを動作させるために、引数が任意の型の配列であることを表現しましょう。

```ts:TypeScript
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // 配列であれば 'length' を持つため、エラーにならない
    return arg;
}
```

もしくは、以下のようにも書くことができます。

```ts:TypeScript
function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array は 'length' を持つため、エラーにならない
    return arg;
}
```

## Generic Types
ジェネリック関数の型はそうでない関数の宣言と似ていますが、最初に型変数の宣言を記述する点が異なります。

```ts:TypeScript
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;
```
>`<T>(arg: T) => T` がジェネリック関数の型

型変数名は他の名前でも問題ありません。

```ts:TypeScript
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <U>(arg: U) => U = identity;
```

また、オブジェクトリテラルのように書くことも可能です。

```ts:TypeScript
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: {<T>(arg: T): T} = identity;
```

次に、ジェネリックなインタフェースを作成してみましょう。
先ほどのオブジェクトリテラルによる宣言をインタフェース内に記述します。

```ts:TypeScript
interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;
```

型変数をインタフェース全体に適用させることも可能です。
その場合、他のメンバでも型変数を共用できるようになります。

```ts:TypeScript
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
```

また、ジェネリックなクラスを作成することも可能ですが、ジェネリックな Enum、名前空間は作成することはできません。


## Generic Classes
ジェネリッククラスはジェネリックインタフェースと同じように、クラス名の後ろの角括弧 (`<>`) に型変数を記述します。

```ts:TypeScript
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
```

この例を見て分かるように、`GenericNumber` は数値型に限定されることはありません。
他に文字列や、より複雑なオブジェクトに対して使用することもできます。

```ts:TypeScript
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); // test
```

[クラスの章](http://qiita.com/murank/items/7e8f15225bc8259e9bf3) で述べたように、クラスはインスタンスメンバと静的メンバの 2 種類のメンバを持ちますが、ジェネリッククラスの型変数は静的メンバで使用することはできません。

## Generic Constraints
ジェネリック関数を使う時に、型の持つメンバを使用したいと考えるかもしれません。
例えば、先ほどの `loggingIdentity` の例では `arg` 引数の `length` プロパティを使用したかったのですが、引数に指定された型が必ずしも `length` プロパティを持つとは限らないため、使用することはできませんでした。

```ts:TypeScript
function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // エラー。T 型は length プロパティを持たない
    return arg;
}
```

そこで、あらゆる型を受け付ける代わりに `length` プロパティを持つ型だけを受け付けるように制限すれば、 `length` プロパティを使おうとしても問題ありません。
これを実現するには `length` プロパティを持つインタフェースと `extends` キーワードを使用します。

```ts:TypeScript
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // arg が length プロパティを持っていることを知っているため、これはエラーにならない
    return arg;
}
```

これにより、このジェネリック関数に指定可能な型が制限されるため、任意の型に対して使用することはできなくなります。

```ts:TypeScript
loggingIdentity(3);  // エラー。数値型は length プロパティを持たない
```

代わりに必要なプロパティを持つ型を渡す必要があります

```ts:TypeScript
loggingIdentity({length: 10, value: 3});
```

## Using Type Parameters in Generic Constraints
型引数を指定する時に、他の型引数に依存した型引数を指定することも可能です。

例えば、指定されたオブジェクトに存在するプロパティのみを引数に受け取りたい場合、以下のように記述します。

```ts:TypeScript
function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // OK
getProperty(x, "m"); // エラー。'm' 型の引数は 'a' | 'b' | 'c' | 'd' 型に代入できない
```
>説明とは直接関係ないけど、オブジェクトの持つプロパティを型として切り出せるのか。
>これは TypeScript 2.1 からの新機能らしい。

## Using Class Types in Generics
ジェネリック関数でファクトリを受け取る場合、型引数としてコンストラクタ関数の型を指定する必要があります。

```ts:TypeScript
function create<T>(c: {new(): T; }): T {
    return new c();
}
```
>引数を `new c()` の形で使うためには、コンストラクタ関数である必要がある。となると当然引数の型はコンストラクタ関数の型になるわけだ。

コンストラクタ関数とクラスのインスタンスメンバ間の、もう少し複雑な制約の例を見てみましょう。

```ts:TypeScript
class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // 型チェック!
createInstance(Bee).keeper.hasMask;   // 型チェック!
```
>`{new(): T;}` の代わりに `new () => A` でも OK ということかな?