カリー化ってなんだろう?

こんにちはKUJIRAです。今日は以前、クロージャの記事で少し触れたカリー化について話したいと思います。( ちなみに画像は定番のネタです。今回の話とは全く関係ありません

これも普通に手続き型のプログラムを組んでいると中々使う機会のないもので、理解するまでには時間がかかると思います。Wikipediaにも数式とか書いてあって最初から読むのを諦めるレベルです。

Wikipediaの説明を読んでみる

我らのWikipediaには以下のように記載されています。

カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。

はい、また訳のわからない日本語が炸裂しております。

複数の引数をとる関数を〜

前提はこれ。これが、

引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)

こうなる訳です。正直言って前提条件しか分かりません。マジで日本語でおk。

まず、この文章を分からなくしているのが 「もとの関数」 というワードです。

「もとの関数の最初の引数」が引数 で、 「もとの関数の残りの引数をとって結果を返す関数」が戻り値 になるようにしなければいけないらしいです。

ちょっと例を示しながら説明します。例えば複数の引数を持つ関数があります。( 例によって今回もScalaのコードです

これを もとの関数 とした時、 「最初の引数」が引数 ということは二つ目以降の引数に目を瞑ると以下のようになります。

で、これに対して 「元の関数の残りの引数をとって結果を返す関数」が戻り値 になるようにすると以下のようになります。

これがカリー化です。最初の引数(上記のfirstStr)を引数にとり、2番目の引数をとって結果を返すメソッドをリターンしています。

「おいおい、ちょっと待ってくれよ。これちゃんと同じ結果になるの?」と思う方もいらっしゃるかもしれません。なぜならこの記述だと、firstStrとsecondStrが全く別スコープにあるような感じがするからです。そこで思い出して欲しいのが前回のクロージャの記事です。 クロージャが解決できる変数はクロージャが引数で受け取った値と、クロージャ内で定義された値、および、クロージャの外で定義された値でした。 なので、この定義は結果として出力される結果は変わりません。

さて、こんな定義のされ方をしているこのメソッド、一体どうやって実行するんだよ?ともなると思います。このメソッドはScalaでは以下のように実行します。

メソッドに対して区切られた形で引数を渡しています。一つ目を引数で渡した後、無名関数が返ってくるので、それにさらに引数を渡している感じを表現している・・・気がしますw

上記のコード、実はさらに簡略化することができます。

こっちの実装の方が、実際に使用する時と形が似ているのでしっくりくると思います。

さて、ここまでカリー化について話しましたが、ここでカリー化を行う上でよく起こる勘違いについて話したいと思います。よくする勘違いとしては、元のメソッドが複数の引数をとってしまうことです。

例えば、

というのはカリー化ではないです。冒頭に載せたカリー化の定義ですが、意味としては 「複数の引数をとる関数」に対して「最初の引数を引数」にとり「残りの引数をとって結果を返す関数」を戻り値にする でした。

なので、元の関数からカリー化を行う際は引数が一つになるようにしなければいけません。上の例だと引数を複数とっているためカリー化にはならないということが分かります。逆に以下はカリー化ができている状態です。

ただ、通常は一回カリー化したらとことんカリー化する場合が多いので、以下のように内包している無名関数もカリー化してしまいます。

どういう使い方をするのか?

以上までのことを踏まえ、実際にKUJIRAがどう使っているかについて話したいと思います。

KUJIRAはこのカリー化を使う時には引数の部分適用を利用したり、クロージャと併用して利用することが多いです。以下に例を示します。

これは以前、クロージャの記事で書かせていただいたサンプルです。ここで出てくる printAndCountListWithFilterメソッド をカリー化を利用してもう少し汎用的なメソッドに変えてみたいと思います。

ポイントは前回まで普通の関数で定義されていた printAndCountListWithFilter をクロージャにしているところです。変数に格納しているのはカリー化された無名関数です。

ここで興味深いのが無名関数の書き方です。

ここでの無名関数の書き方は先ほど記載した 簡略化する前の「testMethod」 の書き方に準じています。簡略した書き方だと無名関数では使えないので注意が必要です。

この実装だと、「printAndCountListWithFilter」はフィルターをかける対象となる文字列のリストを任意に設定することができるので、以下のように友達のリストから特定の名前に該当する人を抽出することも可能です。

このプログラムの実行は以下の通りです。

てすと太郎
てすと二郎
抽出した友達の数:2

まとめ

今日はカリー化について書きました。カリー化って結局のところ

  • メソッドを返すメソッド
  • カリー化対象のメソッドは引数を一つしかとらない
  • カリー化は部分適用と併用すると便利に使える

に尽きると思います。クロージャやカリー化、部分適用などの機能を使って効率よくプログラムが組めるととても楽しいので、この記事を読んで少しでも理解して体得への手がかりにしていただければ幸いです。

それでは今日はここまで。

コメントを残す