[EffectiveJavaScript輪読会4]関数をカリー化するには、bindを使う

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のおさらい

前回は、「固定レシーバを持つメソッドを抽出するにはbindを使う」をお届けしました。

今回は、「関数をカリー化するには、bindを使う」 をお届けします。

テキスト第3章「関数の扱い」の項目26に対応しています。

今日のアジェンダ

  • 引数を一つにしよう
  • 部分適用
  • 再びbindメソッド

引数を1つにしよう

このブログでもたびたび登場する「関数型プログラミング」ですが、そのテクニックのひとつに関数の引数は1つだけにする、というものがあります。

そんなまさかと思いますが、このように、xとyを仮引数に取るばあいでも、引数を1つしか取らない関数に分解することができます。

function myFunction4_26_01() {

  const add = (x, y) => x + y;
  console.log(add(1, 2)); //3

  //カリー化(旧書式)
  function func(x) {
    return function (y) {
      return x + y;
    }
  }
  console.log(func(1)(2)); //3

  //カリー化(ES6)
  const curry = x => y => x + y;
  console.log(curry(1)(2)); //3

}

このように、引数を1つしか取らない関数にすると、値を持った関数を戻り値にもつことができます。

部分適用

このテクニックを部分適用と言います。

function myFunction4_26_02() {

  //カリーの辛さとご飯重量(g)を決める関数
  const curry = x => y => `辛さ${x}番:ご飯${y}g`;
  console.log(curry(3)(200)); //辛さ3番:ご飯200g

  const spycy1 = curry(1);
  const spycy2 = curry(2);
  const spycy3 = curry(3);

  //辛さ1番で大盛り
  console.log(spycy1(300)); //辛さ1番:ご飯300g

  //辛さ1番で普通
  console.log(spycy1(200)); //辛さ1番:ご飯200g

  //辛さ3番で小盛り
  console.log(spycy3(120)); //辛さ3番:ご飯120g

}

これって、以前、クロージャの項でも登場しましたね。

function myFunction2_11_05() {

  function makeSandwich(vegetable) {
    return function (topping) {
      return vegetable + 'and' + topping;
    };
  }

  const lettuceAnd = makeSandwich('レタス');
  console.log(lettuceAnd('トマト')); //レタスandトマト
  console.log(lettuceAnd('バジル')); //レタスandバジル

  const eggAnd = makeSandwich('エッグ');
  console.log(eggAnd('トマト')); //エッグandトマト
  console.log(eggAnd('バジル')); //エッグandバジル

}

よくみると、もともとは以下のように、変数[vegetable]と変数[topping]という2つの引数を取っていた、とも思えます。(結果論にすぎませんが)

function myFunction4_26_03() {

  const makeSandwich = vegetable => topping => vegetable + 'and' + topping;

  const lettuceAnd = makeSandwich('レタス');
  console.log(lettuceAnd('トマト')); //レタスandトマト
  console.log(lettuceAnd('バジル')); //レタスandバジル

  const eggAnd = makeSandwich('エッグ');
  console.log(eggAnd('トマト')); //エッグandトマト
  console.log(eggAnd('バジル')); //エッグandバジル

}

ちょっと、本題からそれてしまいました。

再びbindメソッド

この、部分適用ですが、もともとこのように書くしかありませんでした。

しかし、bindメソッドの登場により、スッキリ書けるようになりました。

function myFunction4_26_04() {

  //部分適用の条件式
  function greeting(x, y) {
    if (typeof y === 'undefined') {
      return function (y) {
        return x + y;
      };
    }
  }

  let test = greeting('こんにちは');
  const taro = test('太郎');
  console.log(taro);

  //bindでスッキリ書ける
  function mul(a, b) {
    return a * b;
  }
  let double = mul.bind(null, 2);
  console.log(double(3));
  console.log(double(2));

}

テキストは、この部分適用を、thisという角度から眺めてみようというお話です。

bindを使うと、thisというレシーバをメソッド(関数)に結合することができるよねという切り口です。

bindの使い方はこうです。

メソッド.bind(第1引数に、thisの結合先のオブジェクト、第2引数に、bind先に渡す引数。)

最後にテキストの写経もしておきましょう。

function myFunction4_26_05() {

  const paths = ['index.html', 'info.html', 'profile.html'];

  const makeSimpleURL_ = (protocol, domain, path) => {
    return `${protocol}://${domain}/${path}`;
  }

  //これでも書ける
  const urls = paths.map(path => makeSimpleURL_('http', 'tonari-it', path));
  console.log(urls); //[ 'http://tonari-it/index.html',  'http://tonari-it/info.html',  'http://tonari-it/profile.html' ]

  //でもね。これでも書けるよ。
  const urls2 = paths.map(makeSimpleURL_.bind(null, 'http', 'tonari-it'));
  console.log(urls2); //[ 'http://tonari-it/index.html',  'http://tonari-it/info.html',  'http://tonari-it/profile.html' ]
}

まとめ

以上で、「関数をカリー化するには、bindを使う」をお届けしました。

「カリー化」ということばを一度も使いませんでしたが、カリー化とは必要な引数の固定部をもとに、委任される関数を作ることで、以下の2つの働きがあります。

  • 引数の部分集合を結合するテクニック
  • 部分適用の話

この2つを混同してる人が多い、と書いている記事も見かけますが、わたし的には「どっちでもいいかな」と思いました。

lodashという有名なライブラリには、カリー化を行うメソッドが提供されています。

Lodash Documentation

恐らく、カリー化とは引数の部分集合を結合するテクニックのことを言いますが、部分適用で実用されることが多いのではないでしょうか。

それよりなにより、関数型プログラミングの 「複数引数を取るな」 というのがおもしろかったです。

次回は、「コードをカプセル化するには、文字列ではなくクロージャを使う」 をお届けします。

参考資料

JavaScript開発者のための関数型プログラミング—カリー化

このシリーズの目次

タイトルとURLをコピーしました