キーワードnewが嫌いだって話をしました。僕、returnも嫌いなんですよ。
メイヤー先生の言い分
ここんところネタにし続けたメイヤー先生に再び登場してもらいます。メイヤー先生は、「returnはよろしくない」理由を3つあげています(『入門』の(5.8.4)、ただし以下の箇条書きは文責:檜山)。
return 変数
という形を使うとき、この変数は、計算の中間結果を保持するためにだけ存在している。- return文をあっちこっちに散りばめた、複数の出口を持つ関数が作られやすい。
- 関数が実行した最後の文がreturn文でないとき、関数の戻り値が何であるべきか定義する必要がある。
1番目は、これだけだと何言ってるかわかりません。メイヤー先生は、「中間結果を保持する変数を使うなら、そういう変数は“戻り値専用”に決めてしまえ」と言っているのです。実際、Eiffelで採用した方法は、予約された変数Resultです。
2番目は、「そうかも知れない」と思うけど、3番目は、「何であるべきか定義」すればいいだけと思います。いずれもイマイチ説得力に欠けます。
「めんどくさい」という理由
僕がreturnがイヤな理由は、まず「書くのがめんどくさい」から(newのときと同じだ)。んで結局、PerlやRubyのように「最後に評価された式が戻り値となる」でいいのじゃないかと思います。メイヤー案と比べてみると、明示的な変数Resultは導入しませんが、式文(式だけからなる文、値は捨てられる)があったときは、あたかもResult := 式
(Resultへの代入文)のように解釈することになります。
この方式を採用すると、「関数の戻り値が、どこで返されているか」が分かりにくくなります。メイヤー方式でもこの弊害はありますが、変数Resultを探して追いかければいいので、目印(Result)があるだけ良いといえます。
こんな弊害がある方法を、「めんどくさい」というイイカゲンな理由だけで推奨はできませんね。でも、デメリットをメリットとみなす理屈があるにはあります。
returnを書かなくても分かりやすくする
returnと書いてあれば、そこで関数から抜けて値が返されるのは明白です。こういう明らかな目印がなくても「関数の出口が分かる」ように書くにはどうしたらいいのでしょう。次のような例題で考えます。
- 関数calcTotalは、数値の列の合計を求める。
- 第1引数numarrayは、nullであるか、または数値の配列(型が指定できるなら Number[]型)。
- 第2引数nは、合計する数値の最大個数(先頭から何個を使うか)を示す。
まずはこれ:
function calcTotal(numarray, n) {
// numarrayがnullのとき0でいいのか? -- それは置いといて
if (numarray == null) return 0; // 1
if (numarray.length == 0) return 0; // 2
if (n <= 0) return 0; // 3
var total = 0;
for (var i = 0; i < n; i++) {
if (i == numarray.length) return total; // 4
total += numarray[i];
}
return total; // 5
}
出口が(字面の上で)5個あります。わざとらしい? (個人的にはコレ、ある意味“わかりやすい”気がしないでもない。)
2番目と3番目のreturnはどうせ不要だから取り除きましょう。for文のなかのreturnはbreakにしてもいいけど、breakもgotoっぽいのでループの上限回数を最初から制限しておくことにします。すると:
function min(x, y) { return (x < y)? x : y; }function calcTotal(numarray, n) {
if (numarray == null) return 0; // 1
var total = 0;
for (var i = 0; i < min(numarray.length, n); i++) {
total += numarray[i];
}
return total; // 2
}
おおー、出口が2個に減った。が、elseのないif文だと、「最後に評価した式を戻り値とする」方式では問題があります。キチっと場合分けして:
function calcTotal(numarray, n) {
if (numarray == null) {
return 0; // 1
} else {
var total = 0;
for (var i = 0; i < min(n, numarray.length); i++) {
total += numarray[i];
}
return total; // 2
}
}
totalの初期化を早めにすれば:
出口が1個になった、しかも関数の最後。これならreturnがなくても紛らわしくはないでしょう。
function calcTotal(numarray, n) {
var total = 0;
if (numarray != null) {
for (var i = 0; i < min(n, numarray.length); i++) {
total += numarray[i];
}
}
return total; // 1
}
メイヤー先生は、こういう出口が少ないコーディングを推奨しているようで、そのとき、totalの代わりに予約された名前Resultを使えば、とおっしゃっています。「最後に評価した式を戻り値とする」方式でも事情は同様で、returnがないことが、よいコーディングへの強制力(むしろ、悪いコーディングへの抑止力)になるかもしれません(強制も抑止も無視されてひどいコーディングになる可能性もあり)。
returnがイヤなほんとの理由
僕が、returnの存在を心底うっとうしく感じるのはラムダ式風の構文においてです。λx.(x + 1)(型付きならλx:int.(x + 1):int)をいくつかの言語で書いてみます。
Scheme:
(lambda (x) (+ x 1))
Caml:
(fun x -> x + 1)
Groovy:
{x -> x + 1}
Python:
lambda x: x + 1
JavaScript:
function (x) {return x + 1}
C++:
<> (int x) -> int {return x + 1;}
JavaScriptとC++(提案)のreturnがいかにも不格好です。コサキさんは<>
と->
を嫌がっていたけど、僕にはreturnの存在のほうが気に障るぅー。