カリー化 - most adequate guide Chapter 4のテキトーな訳
このページの趣旨
下記ページの、かなり「大雑把」に、「私が興味を持ったところだけ」を「雰囲気」で翻訳したものです。
mostly-adequate.gitbooks.io Chapter 04: Currying
原文をかなり崩して書いている(原文にない単語、文章を追加したりしている箇所もある)ので、翻訳とすら、呼べないものかもしれません。
違和感を感じたら、原文を参照することをおすすめします。
Chapter4: カリー化
あなたなしでは生きることはできない
私の父は、かつて、一度手に入れるまでは、持っていなくても平気で生活できるような物がある、と説明してくれたことがあります。電子レンジは、その1つです。スマートフォンは、もう1つのものです。年配の方であれば、インターネットのない充実した生活を思い出すかもしれません。私にとっては、カリー化も、それに匹敵します。
その概念はシンプルです。関数を、その関数が必要とするよりも少ない数の引数で、呼び出すことができるのです。その結果、残りの引数を受け取るような関数が得られます。
あなたは、すべての引数を一気に指定して関数を呼び出すか、少しずつ引数を指定して呼び出すかを選ぶことができます。
const add = x => y => x + y;
const increment = add(1);
const addTen = add(10);
increment(2); // 3
addTen(2); // 12
今回は、引数を1つ指定すると関数が得られるfunction add
を作りました。返されたfunctionは、1つ目の引数をクロージャーを通して記憶しています。
両方の引数を一気に指定するのは大変なので、curry
と呼ばれる特別なヘルパーfunctionを使って、このように簡単に呼び出せるfunctionを定義することができます。
それでは、試しに、いくつかのカリー化されたfunctionを定義してみましょう。ここからは、Appendix A - Essential Function Support.で定義されたcurry
関数を使っていきます。
const match = curry((what, s) => s.match(what));
const replace = curry((what, replacement, s) => s.replace(what, replacement));
const filter = curry((f, xs) => xs.filter(f));
const map = curry((f, xs) => xs.map(f));
ここで適用しているパターンは単純ですが、とても重要です。私は、戦略的に、操作対象となるデータ(たとえば、文字列や配列)を最後の引数として配置しています。その理由は、これを使うときにわかるでしょう。
(/r/g
という構文は、正規表現で、すべての「r」に一致することを表しています。もし、さらに詳細を知りたいときには、more about regular expressionsをご覧ください。)
match(/r/g, 'hello world'); // [ 'r' ]
const hasLetterR = match(/r/g); // x => x.match(/r/g)
hasLetterR('hello world'); // [ 'r' ]
hasLetterR('just j and s and t etc'); // null
filter(hasLetterR, ['rock and roll', 'smooth jazz']); // ['rock and roll']
// xs => xs.filter(x => x.match(/r/g))
const removeStringsWithoutRs = filter(hasLetterR);
// ['rock and roll', 'drum circle']
removeStringsWithoutRs(['rock and roll', 'smooth jazz', 'drum circle']);
const noVowels = replace(/[aeiou]/ig); // (r,x) => x.replace(/[aeiou]/ig, r)
const censored = noVowels('*'); // x => x.replace(/[aeiou]/ig, '*')
censored('Chocolate Rain'); // 'Ch*c*l*t* R**n'
ここで、お見せしたのは、事前に1つまたは2つの引数を指定して「事前に」functionを呼び出して、これらの引数を覚えさせた新しいfunctionを得る機能です。
ぜひ、あなたもMostly Adequate repositoryのGitリポジトリをクローンして、上のコードをコピーして、REPLで実行してみてください。curry関数や、付録に書かれているすべてのものは、support/index.js
モジュールで定義されています。
git clone https://github.com/MostlyAdequate/mostly-adequate-guide.git
カリーは特製のソース
カリー化は、様々な場面で便利に使うことができます。たとえば、さきほどのhasLetterR
、removeStringsWithoutRs
やcensored
のように、元となるfunctionに、いくつかの引数を指定することで、新しいfunctionを作ることができます。
また、1つの要素を操作するあらゆるfunctionを変形して、配列を操作するようにすることもできます。単純にmap
でwrapしましょう。
const getChildren = x => x.childNodes;
const allTheChildren = map(getChildren);
一般的に、functionに、想定されているよりも少ない引数を適用することを、部分適用と呼びます。functionに部分適用をすることで大量のボイラープレート(繰り返し似たようなコードを書くこと)を書かないでも済むようになります。
たとえば、先ほどのallTheChildren
functionを、カリー化されていないlodashのmap
で書くと、どうなるか考えてみましょう(なお、lodashのmapは、先ほど定義したmapとは引数の順番が違うことに注意してください)。
const allTheChildren = elements => map(elements, getChildren);
通常、配列を操作するようなfunctionを直接定義することはありません。なぜなら、単にmap(getChildren)
と呼び出せば、配列を操作ようにできるからです。これは、sort
やfilter
、その他の高階関数を呼び出すときも同じです(高階関数とは、関数を引数または返り値に取る関数です)。
前のChapterで純粋関数の話をしたときに「純粋関数は1つの入力から1つの出力を得る関数だ」という話をしました。カリー化した関数は、まさしくこれに当てはまります。カリー化した関数は、1つの「入力」から(残りの引数の指定が必要な)1つの「新しい関数」が得られます。つまり、1つの入力に対して1つの出力が得られています。
出力が別の関数であっても、純粋関数であることに変わりはありません。また、一度に、複数の引数を指定することもできます。これについては、単に、(表記を簡便にするために)余分な括弧()
を取り除いたものと考えることができます。
まとめ
カリー化は、手軽に使うことができます。そして、私は、毎日のように、カリー化した関数を使って仕事をすることをとても楽しんでいます。カリー化は、関数型言語によるプログラミングをするときの冗長さ、面倒さを軽減するためのツールなのです。
新しい便利な関数を、その場で、(単にいくつかの引数を渡すだけで)作成することができます。おまけに、複数の引数を持つ関数についても、数学的な関数の定義に当てはめられるようになりました。
次のChapterでは、もう1つの基本的なツールであるcompose
を手に入れましょう。
練習問題
練習問題についての注意
この本全体を通して、今回のような「練習問題」の項が出てきます。練習問題は、gitbook経由であれば、ブラウザ内で直接実行することができます(これがおすすめです)。
この本のすべての練習問題では、グローバルスコープに、様々なヘルパー関数が準備されていることに注意してください。具体的には、この本のAppendix A、Appendix B、Appendix Cで定義されているすべてのものを使うことができます。それだけでは足りない場合、いくつかの練習問題では、その問題を解くために使う関数を個別に定義しています。それらの個別に定義された関数も使えるものとして考えてください。
ヒント:ブラウザのフォームで「Ctrl」+「Enter」を押すと、回答を投稿することができます。
あなたのPCで練習問題を解く(オプション)
もし、あなたの手元のエディタを使って、練習問題を解きたいときには、次のようにしてください。
- リポジトリをcloneする(
git clone git@github.com:MostlyAdequate/mostly-adequate-guide.git
) - excercises sectionに行く(
cd mostly-adequate-guide/exercises
) - npmで必要なパッケージをインストールする(
npm install
) - 各chapterのフォルダに入っている「exercises_*」というファイルを修正して、解答を作る
- npmで必要な修正を加える(
npm run ch04
)
ユニットテストが実行され、誤りがあるときには、ヒントが表示されます。また、練習問題の解答は、「exercises_*」というファイルに入っています。
それでは、始めてください!