Function と Procedure

近頃そこかしこで見かけるホット? それともクール? な話題,return null.それを見ていて同じような話題を思い出したので久しぶりにB.Meyerの名著「オブジェクト指向入門」を開きました.っていうか第2版の翻訳は? 訳者の方,自分の著書は出版しているのに... 「この夏には出ます.」とおっしゃっていたのはもう3,4年も前だというのに... 無念だ.
B.Meyerは同書で,関数(function)と手続き(procedure)は明確に区別するべきだと主張しています.

関数(function)
戻り値がある.
オブジェクトの状態*1は変更しない.
手続き(procedure)
戻り値がない.
オブジェクトの状態を変更する.

まったく逆なんですね.
関数というのは「問合せボタン」です.オブジェクトという機械についている問合せボタンを押すと,機械の状態を知ることができます.しかし,このボタンをいくら押しても機械の状態は変わりません.何度ボタンを押しても,確実に同じ結果を得ることができます.
手続きというのは「変更ボタン」です*2.このボタンを押しても機械の状態は分かりませんが,中ではガチャガチャとなにやら動いているようです.その後で「問合せボタン」を押すと,さっきまでとは違った結果を得ることになります.
原則として正しいのだと思います.特に「オブジェクト指向入門」で紹介されているEiffelというプログラミング言語には,表明という強力な概念が備わっています.表明はオブジェクトの状態やメソッドの引数が正しいことを検証するものですから,状態を変更してはいけません.つまり,関数は利用できますが,手続きを利用してはいけません.この二つを明確に区別することで,ある値を検証するために呼び出したメソッドが思わぬ副作用を持っていたなんていう無用のトラブルを避けることができるわけです.素晴らしい.


確かに素晴らしいのですが,現実はなかなかこの原則を全面的に受け入れられなかったりもします.例えば同書に出てくる乱数の場合.
Javaではこうですが,

Random randomGenerator = new Random();
int rand = randomGenerator.nextInt();

nextInt()は乱数を返すと同時にRandomの状態を変更しています.これは原則に反します.よって,次のようでなくてはなりません.

Random randomGenerator = new Random();
randomGenerator.next();
int rand = randomGenerator.intValue();

うーむ.あなたはこれを受け入れられますか?
同じくスタックの場合.
Javaだとこうですが,

Object item = stack.pop();

pop()はスタックの一番上の要素*3を取り除いて,それを返しています.これは原則に反します.よって,次のようでなくてはなりません.

Object item = stack.top();
stack.pop();

うーむ.うーーーむむ.あなたはこれを受け入れられますか?
さて,この原則と return null を同時に踏みにじっている例として,Map#remove(Object)をあげることができます.このメソッドは,Mapに引数をキーとするマッピングが含まれていればそのマッピングを取り除き,マッピングの値を返します.マッピングが含まれていない場合はnullを返します.

Object item = map.remove(key);
if (item != null) {
    ・・・
}

いけませんねー.次のようでなくてはなりません.

if (map.containsKey(key)) {
    Object value = map.get(key);
    map.remove(key);
    ・・・
}

うーむ.うーーーむむ.うーーーーーーーむむむ.あなたはこれを受け入れられますか?


正しい原則であっても,それを常に押し通すことが適当とは限らない,ということなのでしょう.もちろん,原則を知らないとか無視するのとは訳が違います.正しい原則をよぉく理解して,その上で適当な場合には原則を破ることもいとわない.そんな風にありたいものです.
さて自分は? 原則を知らないことが多いかも... 心より恥じる.

*1:この状態は抽象状態のことで,内部的なキャッシュなど具象状態の変更はありです.

*2:オブジェクト指向入門」では「コマンドボタン」です.

*3:スタックというのは下から上に積み上げるものなのです.