どうも。つじけ(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という有名なライブラリには、カリー化を行うメソッドが提供されています。
恐らく、カリー化とは引数の部分集合を結合するテクニックのことを言いますが、部分適用で実用されることが多いのではないでしょうか。
それよりなにより、関数型プログラミングの 「複数引数を取るな」 というのがおもしろかったです。
次回は、「コードをカプセル化するには、文字列ではなくクロージャを使う」 をお届けします。
参考資料
JavaScript開発者のための関数型プログラミング—カリー化
このシリーズの目次
- [EffectiveJavaScript輪読会4]固定レシーバを持つメソッドを抽出するにはbindを使う
- [EffectiveJavaScript輪読会4]関数をカリー化するには、bindを使う
- [EffectiveJavaScript輪読会4]コードをカプセル化するには、文字列ではなくクロージャを使う
- [EffectiveJavaScript輪読会4]関数のtoStringメソッドに依存するのは避けよう
- [EffectiveJavaScript輪読会4]非標準のスタック調査プロパティを使うのは避けよう
- [EffectiveJavaScript輪読会4]prototype、getPrototypeOf、__proto__の違いを理解する
- [EffectiveJavaScript輪読会4]__proto__は決して変更しないこと