0
0

More than 1 year has passed since last update.

missing return statement ~ complete normallyとcan complete normally ~

Posted at

言い訳

筆者はJavaの授業をサボりました。
なるべく仕様書から引っ張ってきましたが、私自身は信用ならないので警戒しつつお読みください。
Qiita初投稿です。

TL; DR

  • 制御フロー関連のエラーが出たら14.22 Unreachable Statements(SE 20のやつ)を読もう
  • complete normallyは実行時の話だよ
  • can complete normallyはコンパイル時の話だよ
  • 筆者にはcomplete normallyの必要性がわからなかったよ
  • error: missing return statementって言われたら
    returnするかthrowしよう

以降のコードはjavac 19、参照した仕様はThe Java® Language Specification
Java SE 20 Edition

発端

確率分布に関するこんなコードを書いてた。

import java.lang.Math;
import java.util.Optional;

// 確率
// 常にvalueは[0, 1]
record Probability
(
	double value
)
{
	public static Optional<Probability> from(double value)
	{
		return (value >= 0.0 && value <= 1.0) ? Optional.of(new Probability(value)) : Optional.empty();
	}
}

// 離散的な確率分布
// probabilitiesの総和は1
record ProbabilityDistribution
(
	Probability[] probabilities
)
{
	int generate()
	{
		double cumulative_p = Math.random();

		int index = 0;
		double cumulative_dist = 0.0;
		for(Probability p_dist : probabilities)
		{
			cumulative_dist += p_dist.value();
			
			if(cumulative_p < cumulative_dist) return index;
			else ++index;
		}
	}
}

class Hoge
{
	public static void main(String[] args)
	{
		ProbabilityDistribution p_dist = new ProbabilityDistribution(new Probability[]{new Probability(0.1), new Probability(0.2), new Probability(0.3), new Probability(0.4)});
		int[] count = new int[4];
		for(int i = 0; i < 10000; ++i)
		{
			++count[p_dist.generate()];
		}

		for(int i = 0; i < 4; ++i)
		{
			System.out.println(count[i]);
		}
	}
}

Probabilityは確率を表す型で、ProbabilityDistributionは確率分布。
ProbabilityDistributionのgenerateは確率分布をもとに確率変数の値を生成する。

$ javac hoge.java -J-Duser.language=en
hoge.java:37: error: missing return statement
        }
        ^
1 error

あれ、戻り値返さないと実行時エラーじゃなくてコンパイル時エラーなんだ。まあ確かに、returnすることを静的に保証できてないしね。
して、これはどういう判断基準なんだ?どう対処しよう?

エラーをコピッてググったところ、SE specificationの8.4.7を見ればいいらしいとわかった。(SE 20版)

SE specificationの読解

(一応筆者の流し読みを載せるので、読み飛ばしても大丈夫)

8.4.7. Method Body

If a method is declared to have a return type (§8.4.5), then a compile-time error occurs if the body of the method can complete normally (§14.1).

ほう、メソッドの本体が戻り値を返さないような状況はcomplete normallyと表現されるのか。14.1を見てみよう。

14.1. Normal and Abrupt Completion of Statements

If all the steps are carried out as described, with no indication of abrupt completion, the statement is said to complete normally. However, certain events may prevent a statement from completing normally:

The break, yield, continue, and return statements (§14.15, §14.21, §14.16, §14.17) cause a transfer of control that may prevent normal completion of expressions, statements, and blocks that contain them.

Evaluation of certain expressions may throw exceptions from the Java Virtual Machine (§15.6). An explicit throw (§14.18) statement also results in an exception. An exception causes a transfer of control that may prevent normal completion of statements.

If such an event occurs, then execution of one or more statements may be terminated before all steps of their normal mode of execution have completed; such statements are said to complete abruptly.
(中略)
The terms "complete normally" and "complete abruptly" also apply to the evaluation of expressions (§15.6).

なるほど?どうも、ある文や式が全部実行されることをcomplete normallyといい、途中で抜けることをcomplete abruptlyというみたいだ。で、returnする場合はcomplete abruptlyになるらしい。

If a statement evaluates an expression, abrupt completion of the expression always causes the immediate abrupt completion of the statement, with the same reason. All succeeding steps in the normal mode of execution are not performed.

Unless otherwise specified in this chapter, abrupt completion of a substatement causes the immediate abrupt completion of the statement itself, with the same reason, and all succeeding steps in the normal mode of execution of the statement are not performed.

Unless otherwise specified, a statement completes normally if all expressions it evaluates and all substatements it executes complete normally.

原則、ある文の中の部分文や部分式がcomplete abruptlyした場合はもとの文もcomplete abruptlyするらしい。で、原則として部分文や部分式がcomplete normallyすればもとの文もcomplete normallyするようだ。

今回エラーが出た原因となりそうなのは、メソッド本体のBlocks, if-then statement, for statementあたりだろうか。

14.2. Blocks

A block is executed by executing each of the local variable declaration statements and other statements in order from first to last (left to right). If all of these block statements complete normally, then the block completes normally. If any of these block statements complete abruptly for any reason, then the block completes abruptly for the same reason.

部分式がすべてcomplete normallyすればブロックもcomplete normally、一つでもcomplete abruptlyすればcomplete abruptlyするらしい。驚きはない。

14.9.2. The if-then-else Statement

An if-then-else statement is executed by first evaluating the Expression. If the result is of type Boolean, it is subjected to unboxing conversion (§5.1.8).

If evaluation of the Expression or the subsequent unboxing conversion (if any) completes abruptly for some reason, then the if-then-else statement completes abruptly for the same reason.

Otherwise, execution continues by making a choice based on the resulting value:

  • If the value is true, then the first contained Statement (the one before the else keyword) is executed; the if-then-else statement completes normally if and only if execution of that statement completes normally.

  • If the value is false, then the second contained Statement (the one after the else keyword) is executed; the if-then-else statement completes normally if and only if execution of that statement completes normally.

おや、条件式によってどっちの部分文がif文のcompletion性に関与するかが変わるぞ?
これってコンパイル時にエラー吐けなくない...?

真実は14.22. Unrechable Statementsにあり

The rules in this section define two technical terms:

whether a statement is reachable

whether a statement can complete normally

The rules allow a statement to complete normally only if it is reachable.

...。

complete normallycan complete normallyは違うらしい。

*極東語スラング*!
14.1に飛ばしておいて読むべきは14.22とか、これ罠だろ!!

というわけで、14.22を眺めてみると

(抜粋)

  • The block that is the body of a constructor, method, instance initializer, static initializer, lambda expression, or switch expression is reachable.
    A non-empty block that is not a switch block can complete normally iff the last statement in it can complete normally.
    The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable.
    Every other statement S in a non-empty block that is not a switch block is reachable iff the statement preceding S can complete normally.
  • An enhanced for statement can complete normally iff it is reachable.

上記理由で上のコード内のgenerateメソッドの本体はcan complete normallyなため、エラーとなった。

対処法

最後にreturnthrowを入れよう。

感想

8.4.7のリンクが悪いよこれは。
8.4.7の部分を理解するのに14.1を読む必要ないじゃん。
そもcomplete normally/abruptlyいる?昔の名残が放置されているだけだったりしない?

それから、今回引用してないがifとwhileのreachable性が異なるのもなんだかなぁと思った。C++のif constexpr相当の構文がほしい。

そもそもC++でいうstd::unreachableみたいなものが無いのにコンパイル時エラーにするな、という気もする。
std::unreachableが無理なら言語標準の例外を投げるべきでは。
「メソッド末尾に到達してしまった」というドメインに無関係のロジックミスを、ユーザーの好き勝手な例外での表現するのはどうかと思う。
大半のプログラマは例外すら投げずに雑にreturnするんじゃ?

ちゃんと考えてないが、enhanced forがなんでcan complete normallyなのかよくわからなかった。

ここまでボロクソに言ったが、それでも関数の末尾到達でUBになる某言語より頑張ろうとしている感があって、非常に良かった。
某言語はプログラマに安全性の責任を持たせる代わりに、豊かな表現を提供することを是としているのだろうけど。

上のコードが必ずreturnすること1を静的に保証できる方法がほしい。それさえあればJavaristaもC++erもみんな幸せになる。
どうも他の言語にはそういう機能があるとか。篩型なるもので他言語では解決できるらしい...?

  1. 浮動小数の計算なので、実は必ずreturnするとは言えない気がする。今回はエラーに助けられた形である。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0