ジェネリクス TypeScript & Angular 勉強会 #4

## 前回のおさらい
Typescriptの関数定義は、以下のようなパターンがあります。

```ts
//一般的な関数定義
function greet(name: string): void {
  console.log(`hello, ${name}`)
}

// 関数式
let greet2 = function(name: string): void {
  console.log(`hello, ${name}`)
}

// アロー関数
let greet3 = (name: string) => {
  console.log(`hello, ${name}`)
}

// アロー関数の省略記法
let greet4 = (name: string) => console.log(`hello, ${name}`)
```

迷ったらアロー関数で定義しておくと一旦はよいです。 `this` をきっちり利用する関数を使いたい場合には `function` を使いましょう。


## 関数定義のおさらい
以下のような関数 `filter` を考えます。

- 第一引数に、配列を取る
- 第二引数に、その配列に対するフィルタ関数を取る

上記の条件で関数の型を考えてみましょう。

```ts
//アロー関数定義
let filter: (
    source: unknown[], //引数① もとの配列
    predicate: (input: unknown) => boolean //引数② フィルタリング用の関数
) => unknown[] //引数③ リターン
```

ただし、これだと型の束縛がないので、例えばこのような関数も代入できてしまいます。

```ts
filter = (
    src: unknown[],
    pred: (input: string) => boolean 
): number[] => { //return と pred の型が一致していない
    return []
}
```

もっと型を束縛すればいいのですが、そうすると汎用性がなくなります。

```ts
let filter2: (
    source: number[], //引数① もとの配列
    predicate: (input: number) => boolean //引数② フィルタリング用の関数
) => number[] //引数③ リターン
```

前回話した関数のオーバーロードにて、複数の型の定義はできますが冗長です。

```ts
// 関数のオーバーロード
type Filter = {
    (src: number[], pred: (input: number) => boolean): number[]
    (src: string[], pred: (input: string) => boolean): string[]
    (src: object[], pred: (input: object) => boolean): object[]
}
```

これらの冗長な定義を解決するのが、多くの言語で採用されている**ジェネリクス**です。

---
## ジェネリクス

ジェネリクスとは、一部の型を後から決められるような型のことです。
Filter関数は、ジェネリクスを用いて以下のように定義できます。

```ts
type filter3<T> = (
    source: T[], //引数① もとの配列
    predicate: (input: T) => boolean //引数② フィルタリング用の関数
) => T[] //引数③ リターン
```

上記における `T` を 「型パラメータ」と呼びます。

---

## ジェネリクスの利用
ジェネリクスを利用するには、型を以下のように束縛する必要があります。

```ts
let filter4: filter3<number> = (src, pred) => {
    return src.filter(v => pred(v))
}
```

ただし、型が推論できる場合には自動で型束縛を行うこともできます。

```ts
function filter<T>(src: T[], func: (input: T) => boolean):T[] {
    return [] //仮実装
}

filter([1,2,3,4,5], (_) => true)
```

---

## ジェネリクスの定義方法
以下のように、様々な形での定義が可能です。

```ts
type Filter1<T> = {
    (src: T[], pred: (input: T) => boolean) : T[]
}

type Filter2 = {
    <T>(src: T[], pred: (input: T) => boolean) : T[]
}

//多分これが一番一般的
type Filter3<T> = (src: T[], pred: (input: T) => boolean) => T[]

type Filter4 = <T>(src: T[], pred: (input: T) => boolean) => T[]

function filter5<T>(src: T[], pred: (input: T) => boolean): T[]{
    return []
}
```

デフォルト引数のように、型パラメータの初期値を設定することもできます。

```ts
type FilterWithDefault<T = number> = (src: T[], pred: (input: T) => boolean) => T[]
```

---

## 複数の型パラメータを持つジェネリクス
ジェネリクスには、複数の型パラメータをもたせることもできます。
例えば、以下のような関数 `map` を考えてみましょう。

- ソースとなる配列を第一引数に、配列の要素を変換する関数を第二引数に取る
- 変換された結果を戻り値とする

この場合、ソースの配列の型だけでなく、戻り値の型も指定をする必要があります。

```ts
type Map<T,U> = (src: T[], func:(input:T) => U) => U[]

```

---
## ジェネリック型エイリアス
ここまで関数を中心に見てきましたが、関数以外の型に対してもジェネリック型の定義はできます。
たとえば、複数の`Filter` を管理する `FilterType` という型を考えてみましょう。

```ts
type Filter<T> = (src: T[], pred: (input: T) => boolean) => T[]
type FilterPipe<T> = {
    filters: Filter<T>[]
    apply: (src: T[]) => T
}
```

---

## 制限付きのジェネリクス型
ジェネリクスを利用したいときでも、あらゆる値が入るのではなく、特定の型に制限したい場合があります。
例えば、これまで扱ってきた `Filter` について、文字列と数値にしか適用したくない、という形になった場合、以下のような書き方で制限することができます。

```ts

type LimitedFilter<T extends string | number> = (src: T[], pred: (input: T) => boolean) => T[]

type stringFilter = LimitedFilter<string>
type objectFilter = LimitedFilter<Object> //Type 'Object' is not assignable to type 'number'.(2344)
```

---

## 練習問題
*プログラミングTypescriptより*

> 型安全なアサーション関数、isを実装してください。型で概略を記述することから始めます。
> これは、完成したら、次のように使えるものです。

> ```ts
> 
> // stringとstringを比較します 
> is('string', 'otherstring') // false
> 
> // booleanとbooleanを比較します
> is(true, false) // false
> 
> // numberとnumberを比較します
> is(42, 42) // true
> 
> // 異なる型同士を比較すると、コンパイル時エラーになります
> is(10, 'foo') // エラー TS2345: 型 '"foo"' の引数を型 'number' の
>               // パラメーターに割り当てることはできません。
```
---
## 回答例

```ts
function is<T>(left: T, right: T):boolean {
  return left === right
}
```

---

## 演習問題
練習問題のコードを改良して、任意の数の引数を渡せるようにしてください。

```ts
is(1, 2, 3) //false
is("type", "type", "type", "type") //true
is("type", 123, {}) //Error
```