TypeScriptに於けるArray.reduceの型推論の種類

## 先に結論
`Array.reduce`の型定義には複数の種類があります。

```ts:node_modules/typescript/lib/lib.es5.d.ts
interface Array<T> {
   // 中略 一部抜粋
  reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
  reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
}
```

参考: https://github.com/microsoft/TypeScript/blob/v3.6.3/lib/lib.es5.d.ts#L1168

超おおざっぱに言うと、`Array<T>.reduce`が配列の要素と同じ型`T`を返すか、そうでない型`U`を返すかの推論の違いがあります。
意図した推論のされ方にならないとコンパイルエラーになる時があります。

### 例1: 配列の要素の型がreduceの初期値と同じ場合
下記は正しいTypeScriptのプログラムです。

```typescript
function counter1(ary: number[]) {
  // acc: number
  return ary.reduce((acc, val) => {
    return acc + val;
  }, 0);
}
```

ここでは、`Array<T>.reduce()`が下記すべて同じ型`T`だと推論しています。

- `reduce()`のコールバック第一引数acc
- `reduce()`のコールバック第二引数val
- `reduce()`の返り値

```typescript
// Array<T>.reduce の型定義(一部抜粋)
interface Array<T> {
  reduce(fn: (previousValue: T, currentValue: T) => T, initialValue: T): T;
}
```

結果、関数内に型アノテーションが一切無くても型推論で型安全性が担保されコンパイルも通ります。

### 例2: 配列の要素の型がreduceの初期値と異なるが、互換性はある場合
引数の型を`ary: number[]`から`ary: (number | undefined)[]`と変更してみました。

すると`Object is possibly 'undefined'.`となってエラーとなります。

```typescript
function counter2(ary: (number | undefined)[]) {
  // acc: number | undefined と推論される
  return ary.reduce((acc, val) => {
    if (val === undefined) return acc;
    return acc + val; // accで `Object is possibly 'undefined'.` エラー
  }, 0);
}
```
`Array<T>.reduce()`のコールバック第一引数accの型が、配列の要素`T`と同じ型`number | undefined`だと推論されて、結果returnでエラーになっていることがわかります。
(ここでは、`reduce`の初期値と同じ型`number`にしたい)

これはaccの型を明記してあげることで解決します。

```typescript
function counter2Fixed1(ary: (number | undefined)[]) {
  // acc: number
  return ary.reduce((acc: number, val) => {
    if (val === undefined) return acc;
    return acc + val;
  }, 0);
}
```
もしくは、`Array<T>.reduce<U>()`のようにジェネリクスでreduceに型`number`を渡してあげても解決します。

```typescript
function counter2Fixed2(ary: (number | undefined)[]) {
  // acc: number
  return ary.reduce<number>((acc, val) => {
    if (val === undefined) return acc;
    return acc + val;
  }, 0);
}
```

`reduce<U>`に渡した`U`は、accumulatorとreduce自体の返り値の型として使われます。

```typescript
// Array<T>.reduce<U> の型定義(一部抜粋)
interface Array<T> {
  reduce<U>(fn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
}
```

### 例3: 配列の要素の型がreduceの初期値と異なり、互換性が無い場合
下記は正しいプログラムです。

```typescript
function counter3(ary: [string, (number | undefined)][]) {
  // acc: number
  return ary.reduce((acc, [_key, val]) => {
    if (val === undefined) return acc;
    return acc + val;
  }, 0);
}
```

最初から、配列の要素の型`[string, number | undefined]`と初期値の型`number`に互換性が無いため、`Array<T>.reduce<U>()`として推論されるようです。
結果、例2とは異なり型アノテーションが一切無くても型推論がちゃんと動作します。

[TypeScript Playground](https://www.typescriptlang.org/play/?target=1#code/GYVwdgxgLglg9mABBO4oFMBOBGAFAQ0wE8AuRMEAWwCMsBtAXQEpEBvAKES8U3ShExJCRAHS8AJiAjpcBCBAA0iAG74ANiwC8APjaduB3v0GJ88xAGoV6gNz6uAXyUAGJnYft2oSLATJUYBiYAEwExGS4FDRYiAA+iODi6MAwYOjiTIwsHIZ8AkLEYulSMnKK1hqIOnoGBjDAiLiqalWamglgSSlpGTx5JmYQdrXcRvmm5lbNw9xOiK7unt7Q8EgoaFjBAGIwAB7peMIRUbSYcR1dqemZzDV9xgWiEiWyg2QnWErNWro53PWNZqtdqJZJXXpjAbyGZcSFCSYVGZzBbsDxecArPzrQKbHb7cShI6ND5neKg7rXLJ3OGmQrPaQAHhJ2le8i+6h+dy4AKa6mBFzBPRYNMGMPu40GlkR+mRblRnnRPlW-g2mAAzGFSIg6ABnKCYVIAcyUkSop3O5PBTAYVL+sP6jyKkmkrPKdAA+gBrdBEdlqW7VO21HlAtogzqC67iqFDey1EUI6Zx2WLdhAA)で、上記の結果を試すことができます。

## Object.valuesとObject.entriesの例

実際には、`Object.values()`と`Object.entries()`に対してそれぞれ`reduce`を使った時にこのような違いが観測できます。

```typescript
type FamilyAges = {
  ichiro?: number,
  jiro?: number,
  saburo?: number
}

const ages: FamilyAges = {
  ichiro: 11,
  jiro: 22
}

// Object.values(ages): (number | undefined)[]
Object.values(ages).reduce((acc, val) => {
  if (val === undefined) return acc;
  return acc + val; // accで `Object is possibly 'undefined'.` エラー
}, 0);

// Object.entries(ages): [string, (number | undefined)][]
Object.entries(ages).reduce((acc, [_key, val]) => {
  if (val === undefined) return acc;
  return acc + val; // OK
}, 0);
```

## 参考
- [Array\.prototype\.reduce\(\) \- JavaScript \| MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)
- https://github.com/microsoft/TypeScript/blob/v3.6.3/lib/lib.es5.d.ts#L1168