JavaScript で遅延評価をやってみる

遅延評価とは

遅延評価は必要になった時に計算をすることです。

JavaScriptでは値ではなく関数を使用することでこれを実現できます。

シンプルな例

具体例をみます。

const add = (x, y) => x + y;

add(3, 4); //7

add は二つの引数を足した値を返すシンプルな関数です。

では次のように引数を渡した場合、計算の順番はどのようになるでしょうか。

add(1+2, 2+2); //7

以下のような流れになります。

  1. 引数の一つ目の1+2が計算され3が算出される
  2. 引数の二つ目の2+2が計算され4が算出される
  3. 関数内の処理に移り、3+4が実行されて7が算出される

次にadd関数を少し変更します。

const add = (flag, x, y) => {
  if(flag) return x + y;
  return 0;
};

第一引数がtrueの場合はx+yを計算した結果を返し、falseの場合は0を返します。

add(true, 1+2, 2+2); //7
add(false, 1+2, 2+2); //0

よく考えてみると、false の場合は1+2や2+2の処理はやらなくも問題ありません。

むしろ、1+2や2+2の処理がもっと複雑で時間がかかる処理の場合(例えば大きいファイルからデータを読み込むなど)、無駄な処理を行っていることになります。

ここで登場するのが遅延評価です。

遅延評価を使って、第一引数がtrueのときのみ引数の計算を実行するようにしてみます。

冒頭で書いたようにJavaScriptで遅延評価を行う際には、値ではなく関数を引数に渡します

以下はこれまでのaddと同じ結果になります。

const add = (flag, x, y) => {
  if(flag) return x() + y();
  return 0;
};

add(true,() => 1+2, () => 2+2); //7

先ほどより複雑になったように見えますが、ひとつひとつ見ていきます。

add関数の引数の記述は特に変わっていませんが、xとyは関数を想定しています。

そして、flagがtrueの時は、それぞれの関数の実行結果を足しています。

const add = (flag, x, y) => {
  if(flag) return x() + y(); //xという関数を実行した結果と、yという関数を実行した結果を足している
  return 0;
};

JavaScriptでは関数を値として引数に渡せるために、このようなことができます。

addを使う時は、第二引数と第3引数には関数を渡します。

add(true,() => 1+2, () => 2+2);

もう少し分解すると、以下のようになっているのと同じです。

const xFunc = () => 1+2; //1+2の結果を返す関数
const yFunc = () => 2+2; //2+2の結果を返す関数

add(true, xFunc, yFunc);

このように引数に関数を渡すことで、flagがtrueの時のみ、1+2や2+2の処理が行われるので、無駄な計算を避けることができます。

具体的なサンプル

先ほどと同じように、add関数は第一引数がtrueなら第二引数と第三引数の合計値を返し、そうでない場合は0を返すとします。

const add = (flag, x, y) => {
  if(flag) return x + y;
  return 0;
};

xとyには、すべての要素の値が1で長さが99999の配列の、すべての要素を合計した値を渡すとします。

配列を作成する処理と、配列の全要素を合計する処理を作成しておきます。

//第一引数の長さの配列を作成する。要素の値はすべて1
const createArray = (length) => Array(length).fill(1); 

//配列のすべての要素を合計した値を返す
const sum = array => array.reduce((acc, value) => {
  console.log('current value', value);
  return acc + value;
}, 0);

まず遅延評価を使わない場合です。

const createArray = (length) => Array(length).fill(1);

const sum = array => array.reduce((acc, value) => {
  console.log('current value', value);
  return acc + value;
}, 0);

const add = (flag, x, y) => {
  if(flag) return x + y;
  return 0;
};

const myArray1 = createArray(99999);
const myArray2 = createArray(77777);

const result = add(false, sum(myArray1), sum(myArray2));
console.log(result);

add関数の第一引数はfalseですが、実行してみると「current value 1」というログが大量にでます。

これはsum関数内の処理が動いていることを示しています。つまり無駄な計算が動いています。

言い換えると、第一引数がtrueかfalseかにかかわらず、第二引数、第三引数のsum(myArray1) や sum(myArray2) の処理が実行されてしまっています。

次に遅延評価の場合です。addの第二引数と第三引数には関数を渡すようにします。

const createArray = (length) => Array(length).fill(1);

const sum = array => array.reduce((acc, value) => {
  console.log('current value', value);
  return acc + value;
}, 0);

const add = (flag, x, y) => {
  if(flag) return x() + y(); //変更箇所 xとyは関数
  return 0;
};

const myArray1 = createArray(99999);
const myArray2 = createArray(99999);

const result = add(false, () => sum(myArray1), () => sum(myArray2)); //変更箇所
console.log(result);

addの第一引数がfalseの場合は先ほどの「current value 1」というログが出力されない、つまり、無駄な計算が行われていないことをわかります。

まとめ

遅延評価を使うことで無駄な処理を避けることができる。

JavaScriptで遅延評価を行う場合は引数に値を渡すのではなく関数を渡す。