カリー化とは
カリー化とは、「関数を元に引数の減った関数を動的生成すること」である。
- function f(a,b):=a+b
- function curry(y,m)(a):=y(a,m)
- function g2(a):=curry(f,2)(a)
- g2(1)->3
のように、カリー化ではg2という関数を作ってパラメータbを2とすることで、状況を特定することができ、パラメータを1個で済ませることができる。
ただし、Javaではカリー化を明確にサポートしていないのが現状だ(JDK 7ではクロージャをサポートするという話がある)。その理由は、オブジェクトを生成するにはすべてクラスを一旦定義し、そのクラスのインスタンスとしてオブジェクトを生成するクラス指向に基づいているからであり、関数を定義したらそれが即、オブジェクトとはならないためだ。もっとも、Javaでもクラスやインターフェースを多数用意して、疑似的にカリー化を実現することができる。
昔ながらのオブジェクト指向関連の書籍では、「オブジェクト=メソッド+データ」と説明されているが、オブジェクトは「あるデータに関してカリー化された関数の集合」と捉えることができる。このためJavaでメソッドを呼び出すには、オブジェクト(集合)名、メソッド名、パラメータの順に記述するわけだ(何かについてカリー化された関数の集合から、特定の関数を取り出して、必要なパラメータを渡して、関数の結果を得る)。これがオブジェクト指向が「開かれた世界」への対応手段とされる所以となる。
不安定な部分をカリー化の対象とし、安定した部分はインターフェイスとすることで、クライアント側はサーバの実装をあまり気にすることなく処理を呼び出せるようになる。これは、状況が変わっても対応部分は局所化することを意味し、生産性を維持できることになる。
アプリ開発へのカリー化の適用
アプリ開発での効果をみてみると、構造化設計手法的にレイヤー化しただけでは基本的に下位レイヤーで必要なパラメータも上から下に順次に渡していく必要があるため、非機能要件まで引きずることになる。
例えば、JavaEEを使用する場合、当然JavaEEに付随するパラメータも渡すことになるため、JavaEE上で動かそうとするとJavaSEで動かなくなり、逆にJavaSE上で動かそうとするとJavaEEで動かなくなる。これでは単体テストも難しくなってしまう。
そこでAOPということになるのだが(AOPもカリー化を用いた技術、因みにSeasar2は対象となるクラスを継承したクラスを動的に生成することでAOPを実現している)、AOPを前提にしたレイヤー化では機能要件の単体テストは通すことはできるものの、非機能要件を実装したアドバイス(業務ロジックをフックして呼び出される処理)から使用したいパラメータへのアクセス手段がなくなってしまう。これはWebコンテナからアプリケーションへ環境に関する情報を渡す口はHttpServletのdoGet / doPostで渡されるHttpServletRequest / HttpServletResponseを用いて取得する仕様に起因する。AOPは「ログにしか使用できない」といわれるのは、環境から情報を取得する機能に乏しかったためだ。
そこでコンテキストということになる。コンテキストを直訳すれば、文脈という意味だが、ここでは実行状況を指す。コンテキストはパラメータを生成する役割を担うため、前述のフレームワークではカリー化で状況を特定するパラメータを表現している。状況が変わったら、新しいコンテキストを用いてカリー化し直せばよい。これならアプリケーションロジックを変更することなく状況変化に対応できる。
次にこのコンテキストの具体的な実現手段を前述のお客様フレームワークでの実践を基に考える。実は、Seasar2(厳密にはS2Container)は、Web環境のコンテキスト情報「HttpServletRequest」をどこからでも取り出せるようにThreadごとに保存しているのでこれを利用すればよい。実際、S2DaoやS2StrutsなどはSeasar2を直接利用してこれらの情報を取得している。しかし、それではコンテキストが欲しいコンポーネントは全てSeasar2に依存するため、より抽象的な「WebContextインターフェース」を定義し、その実装として、Seasar2を利用する「SeasarWebContext」という実装を用意した。コンテキストが欲しいコンポーネントは「WebContext」インターフェースを介してSeasar2が保存しているコンテキスト情報を取得するため、Seasar2への依存がなくなる。
これが例えばSpringを使用するように変わったときには、「SpringWebContext」を作り情報を取得するようにすればよい。これより上で動いているコンポーネントは、SeasarかSpringかを意識することなく実装できる。
また、そもそもWebであることさえ意識する必要がないコンポーネントも多数存在するので、更にコンテキストを「WebContext」から切り離して定義することが可能だ。例えば認可に関わるコンポーネント用に「UserContextインターフェース」を用意し、「WebContext」に依存した「WebUserContext」を本番用に「ConstUserContext」を単体テスト用に作成すれば、本番とテストで切り替えて使用したり、更にはバッチ環境に持っていくことも可能になる。
津田氏は最後のまとめとして、「カリー化は昔からある極めて強力な道具であり、この道具がオブジェクト指向プログラミング言語を可能にしたのである。このためプログラマ、設計者、分析者は理解すべきものである」と締めくくった。なお、前述のようにJavaでも擬似的にカリー化は可能であるが、現在のところは関数型言語であれば、1行で済むこともクラスに切り出し、インターフェースを抽出し直し、単体テストケースを用意した上で、更にそれらにJavaDocやコメントを用意することを考えれば、100行くらい記述することになるという。