参照透過性とは
CleanやHaskellといった純粋関数型プログラミング言語では、参照透過性(referential transparencyあるいは参照透明性)という概念が重要になっている。
まず、参照透過性という概念から説明する。
参照透過性というと聞きなれないが、要するに、ある式の評価結果が常に同じになるということ。参照透過性というよりも、評価結果恒常性といった方が理解しやすいかもしれない。
たとえば、現在時刻を取得するgetTimeという関数があるとする。この関数は、異なる時刻に呼び出すと、当然異なる時刻を返す。そのため、この関数の呼び出しは参照透過的でない。正弦関数sinは、同じ引数に対して常に同じ値を返す。そのため、正弦関数の呼び出しは参照透過的だ。
このように同じ式の評価結果が常に一定なら参照透過的だ。一方、時刻を返す関数や、Javaなどのオブジェクト指向言語で(状態を変更可能な)インスタンスの状態を返すメソッドなどには参照透過性がない。まぁ、このように、参照透過性自体はそんなに難しい話ではない。難しくなるのはこれからだ。
なぜ参照透過性が重要か
C#やJava、あるいはPerlやPythonなどの言語では、参照透過性という言葉を見かけることはまずない。ところが、CleanやHaskellといった純粋関数型プログラミング言語では、参照透過性が非常に重要になってくる。なぜだろうか?
参照透過であった方がよい理由は、次の三つだと思う。
- テスト/検証しやすい
- 引数さえ同じなら必ず同じ結果を返すのだから、テストは何パターンかの引数の組み合わせで関数を呼び出し、その結果をチェックするだけでよい。特定の順番で関数を呼び出す必要があるとか、事前にインスタンスに値を設定しなければならないとかいう複雑な話は不要。
- パターンマッチやガード式などで場合分けする際に、副作用なしに評価できる。
- 何回呼んでも結果が同じということは、関数の呼び出しに副作用(状態変更やI/Oなど)がないということだ。副作用がないから何度関数を評価してもプログラムの動作に影響しない。関数型言語は次のようにパターンやガード式というもので関数を場合分けして記述する。次の絶対値を求める関数のコードはガード式で関数<(演算子ではなく関数のひとつ)を呼び出している。
abs a
| a < zero = ~a
| otherwise = a
Javaとかの普通の手続き型言語ならならメソッドの本体内にこの条件分けを書くことになる。
public static int abs(int a) {
/* 説明の主題から外れるのでコメントに...
if (Integer.MIN_VALUE == a) {
throw new ArithmeticException();
}
*/
return a < 0 ? -a : a;
}
こうした、ガード式やパターンマッチに使う関数には、副作用がないことが求められる。の副作用がないことは、C言語、C++、C#、Java、Pythonといったさまざまな言語でassertの条件式を書くときにも当てはまる。
- 並列実行や遅延評価などが可能
- いつも同じ値に評価されるということは、どのタイミングで評価しようと結果が同じということになる。そのため、実際の評価のタイミングを遅らせたり並行に評価したりすることができる。その結果、純粋関数型言語の特徴である遅延評価や並列実行という特徴につながる。参照透過性とは呼ばないが、手続き型言語でもマルチスレッドプログラミングでは、オブジェクトがmutable(変更可能)かimmutable(変更不可能)かは重要な違いとなる。
こうしてみると、参照透過性とは呼ばなくても、手続き型言語でも同じような概念を扱っていることがわかる。
関数型言語は、参照透過性を前提とすることで、遅延評価や独特の関数定義方法など、優れた特徴を手に入れている。
|