kagamihogeの日記

kagamihogeの日記です。

JEP 325: Switch Expressionsをテキトーに訳した

http://openjdk.java.net/jeps/325 をテキトーに訳した。

JEP 325: Switch Expressions

Author   Brian Goetz
Owner   Jan Lahoda
Created 2017/12/04 08:56
Updated 2018/01/29 16:23
Type    Feature
Status  Candidate
Component   specification/language
Scope   SE
Discussion  amber dash dev at openjdk dot java dot net
Effort  M
Duration    M
Priority    3
Reviewed by Alex Buckley
Issue   8192963

Summary

switchを文でも式としても使えるよう拡張し、また、nullの扱い方を改良します。この変更は今までの書き方を単純化するもので、switchパターンマッチング(JEP 305)に備える目的もあります。

Motivation

我々はJava言語にパターンマッチング(JEP 305)を導入するための拡張の準備をしていますが、既存のswitchの微妙ないくつかの点が障害となっており、これはユーザを長年イライラさせ続けているものでもあります。これにはnullの処理(switchは引数がnullだとNullPointerExceptionをスロー)と、swtichが文でしか使えない、があります。場合にもよりますが、式として複数条件分岐*1を表現する方が自然な場合があります。

既存のswitch文の多くの使われ方は本質的にはswitch式のシミュレーションで、各arm*2は共用のターゲット変数に代入するか値を返すかのどちらかをします。

int numLetters;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        numLetters = 6;
        break;
    case TUESDAY:
        numLetters = 7;
        break;
    case THURSDAY:
    case SATURDAY:
        numLetters = 8;
        break;
    case WEDNESDAY:
        numLetters = 9;
        break;
    default:
        throw new IllegalStateException("Wat: " + day);
};

文としてこれを表現するのは、似たようなことの繰り返しになるので、回りくどく間違いやすいです。上記コードの意図は日にちに対応するnumLettersの値を計算をする、というものです。これは、より直感的で、明瞭・安全に書けるのが望ましいです。

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
};

switch式への拡張は更に別の要求へと繋がっています。単純なORパターン形式(上の例で見たもの)、フローアナリシスの拡張(式は常に値を計算するか即時終了する*3)、switch式の各caseで値の算出ではなく例外をスロー可能にする、などです。

Description

式として使えるようにswitch文を拡張します。よくある例としては、switch式は以下のようになります。

T result = switch (arg) {
    case L1 -> e1;
    case L2 -> e2;
    default -> e3;
}

switch式は複合的な式(poly expression)になります。ターゲット型が既知であれば、その型は各armにプッシュダウンします。既知であればswitch式の型はそのターゲット型となり、そうでない場合、各caseの型との組み合わせでスタンドアローンの型が算出されます*4

switch文では、breakがswitchの実行を停止させます。switchで使うために、breakが引数を取れるよう拡張し、その値はswitch式の値になります。

switch式のcaseはbreakを介して式で値を算出しつつ文の実行も出来ます。その場合switch文のように複数の文を書きます。

int result = switch (s) {
    case "Foo":
        break 1;
    case "Bar":
        break 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break 3;
}

ただし、上記のような例は稀で、文は無く単一の式を評価する複数のcase、を想定しています。また、シンタックスシュガーを提供するので、その場合はswitch式は以下のように定義します。

case LABEL -> expression;

元はこうです。

case LABEL: break expression;

よって、上記例は簡潔に書けます。

int result = switch (s) {
    case "Foo" -> 1;
    case "Bar" -> 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break 3;
}

また、switch式では以下のようにも書けます。

case LABEL -> throw e;

元はこうです。

case LABEL: throw e;

よって、以下のようになります。

int result = switch (s) {
    case "Foo" -> 1;
    case "Bar" -> 2;
    default -> throw new IllegalStateException(s);
}

これらの書き方は必要に応じて使い分けられるので、上記のswitch式は以下のようにも書けます。

int result = switch (s) {
    case "Foo" -> 1;
    case "Bar" -> 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        break 3;
}

breakの2つの形式(値有・無)はメソッドのreturnの2つの形式と似ています。両者ともメソッド実行を即時停止し、非voidメソッドでは加えて、メソッドの呼び出し元に値を返す必要があります。(break expression-valuebreak labelのあいまいさは比較的簡単に処理可能です)

switch式のcaseは網羅的である必要があり、とりうる入力に対しarmのうち一つだけが評価される必要があります。これは基本的にはdefault句が必要という意味ですが、enumのswitch式で取りうる値をすべて網羅する場合(and eventually, switch expressions over sealed types)、default句はコンパイラが挿入して、コンパイラenum定義がコンパイル時と実行時で変わったことを示します。(これは現状開発者が手でやっていますが、コンパイラによる挿入は面倒を省いてくれて、エラーメッセージは手で書いたよりも記述的となります)

更にswitchの改良を推し進めて、switchの式と文をある程度同じにします。case句で一つしか書けないのをカンマ区切りのリストで書けるようにします。

switch (day) {
    case MONDAY, FRIDAY, SUNDAY: 
        numLetters = 6;
        break;
    ...
};

引数が参照型(現状ではプリミティブのラッパー・文字列・enumのみ)のswitchでは、case句は明示的にnullを指定できます。

String formatted = switch (s) {
    case null -> "(null)";
    case "" -> "(empty)";
    default -> s;
}

もしswitchcase null句が無い場合、最初のcase句は以下であると解釈されます。

case null: throw new NullPointerException();

この挙動はswitchで参照型を用いる際の現行の振る舞いと一致します。

なおもし余裕があれば、switchで現行では使えないプリミティブ型、float, double, longなど、を使えるようにする拡張もするかもしれません。

Dependencies

Pattern Matching (JEP 305)はこのJEPに依存します。

*1:multi-way conditionalsが原文。訳は俺が勝手につけたもの。言うまでもなくswitch-caseのような複数の条件にマッチするヤツのこと

*2:switchからcaseが伸びる様をarmと表現してると思われ

*3:compute a value or complete abruptlyが原文。良い日本語思い浮かばず

*4:この辺良くわからん。