関数オーバーロード

2020/09/27に公開

はじめに

JavaScriptでは、出力される型が入力される引数の型によって変わることがあります。
TypeScriptではオーバーロード(引数や戻り値が異なるが名称が同一な複数のメソッドを複数定義する)された関数の宣言と、入力値によって変わる関数の出力型を静的な型システムを使うことで具体化します。


問題のコード

type Combinable = number | string; //型エイリアス
function combine(input1: Combinable, input2: Combinable) {
  if (typeof input1 === "string" || typeof input2 === "string") {
    return input1.toString() + input2.toString();
  }
  return input1 + input2;
}

コードを解説すると、combineという関数があり、仮引数にはnumber型かstring型のユニオン型を型エイリアスを用いた記述をしています。
またif文の中ではinput1,input2のどちらか一つでもstringが実引数に渡れば、文字列で返す(toString())ことを意味しています。そうでなければ(input1,input2のどちらもstringではない)、number型で返すというコードです。

実際に関数を呼び出してみます。

const result1 = combine(1, 2);
console.log(result1);//3

const result2 = combine("ROUND", 1)
console.log(result2);//ROUND1

実際に機能しています。
しかし、次のようにドット記法ででメソッドをつなぐとエラーが発生します。

const result1 = combine(1, 2);
console.log(result1.toFixed(2)); //エラー

const result2 = combine("ROUND", 1);
console.log(result2.split('')); //エラー

これはresult1やresult2の型に問題があります。
resut1にマウスホバーしてみると、
app.ts

このようにCombinableです。
これにより数値を小数含めた形で返すtoFixedメソッドや文字列を一文字ずつ配列で返すsplitメソッドは、resultの型をstring型かnumber型どちらかに型を絞り込むことができていないため使用しようとするとエラーが発生します。

ここでの改善策で関数オーバーロードを用います。


関数オーバーロードによるコードの改善

先に関数オーバーロードによって改善したコードを提示します。

type Combinable = number | string;

function combine(input1: number, input2: number): number;
function combine(input1: number, input2: string): string;
function combine(input1: string, input2: number): string;
function combine(input1: string, input2: string): string;
function combine(input1: Combinable, input2: Combinable) {
  if (typeof input1 === "string" || typeof input2 === "string") {
    return input1.toString() + input2.toString();
  }
  return input1 + input2;
}

const result1 = combine(1, 2);
console.log(result1.toFixed(2)); //3.00

const result2 = combine("ROUND", 1);
console.log(result2.split("")); //(6) ["R", "O", "U", "N", "D", "1"]

これは正しく動作します。
なぜなら、result1result2の型が先程とは違い、一つ(numberかstring)に絞られているからです。
app.ts

このように関数を定義する前に記述している4つの場合分けを関数オーバーロードといいます。
関数型を定義するように、直感的でわかりやすいです。ここでは4つの関数オーバーロードを定義していますが、必要ない場合分けがあれば省略可能です。


関数シグネチャ(コールシグネチャ)による書き換え

最後に先程の関数オーバーロードの例を関数シグネチャで書き換えたコードを提示します。

//関数シグネチャの利用
type Combinable = number | string;
interface Combine {
  (input1: number, input2: number): number;
  (input1: number, input2: string): string;
  (input1: string, input2: number): string;
  (input1: string, input2: string): string;
}
const combine: Combine = (input1: any, input2: Combinable) => {
  if (typeof input1 === "string" || typeof input2 === "string") {
    return input1.toString() + input2.toString();
  }
  return input1 + input2;
};

const result1 = combine(1, 2);
console.log(result1.toFixed(2)); //3.00

const result2 = combine("ROUND", 1);
console.log(result2.split("")); //(6) ["R", "O", "U", "N", "D", "1"]

以上のように4つの関数オーバーロードをCombineというインターフェースでラップすることで先程のコードと同じように機能します。

Discussion

ログインするとコメントできます