39
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-10-08

先に結論

Array.reduceの型定義には複数の種類があります。

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のプログラムです。

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

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

  • reduce()のコールバック第一引数acc
  • reduce()のコールバック第二引数val
  • reduce()の返り値
// 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'.となってエラーとなります。

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の型を明記してあげることで解決します。

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を渡してあげても解決します。

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自体の返り値の型として使われます。

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

例3: 配列の要素の型がreduceの初期値と異なり、互換性が無い場合

下記は正しいプログラムです。

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で、上記の結果を試すことができます。

Object.valuesとObject.entriesの例

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

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);

参考

39
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?