こんにちは、びしょ~じょです。 気づけば4月になっていました。 で?

ありがとう! GoConference2024はオフライン開催です!!

ということで今日はジャバの話です。 逆張っていくわけではなくて、前々から書こうと思っていたが執筆中にacceptのメールが来たんで、人生はタイミングです。

1. はじめに

あなたがちょうど10年ほど前T大1の情報科学類生だったとき、『データ構造とアルゴリズム』という講義を受けてましたね2。 そこで使われていた言語は? そう、ジャバ。 2013年のジャバの最新バージョンは7でした。 ときは流れ2024年、あなたはご存知ですか。ジャバの最新バージョンを。 なんと…22です!

今回は15バージョン分の遅れを取り戻していく。

2. 題材

今回はこちら

とりあえずラムダ計算を実装していく。 例によってパーザは実装していない。

\\[ \begin{array}{rcl} % the syntax of lambda calculus x &\in& \text{Var} \\ t & ::= & x \mid \lambda x.t \mid t_1t_2 \\ v & ::= & \lambda x.t \mid v_1v_2 \\ \theta & ::= & \emptyset \mid \theta[x \mapsto v] \\ \newline % the semantics of lambda calculus with environment \langle x, \theta \rangle &\to& \theta(x) \\ \langle \lambda x.t, \theta \rangle &\to& \lambda x.t \\ \langle t_1t_2, \theta \rangle &\to& \langle t_1, \theta \rangle \langle t_2, \theta \rangle \\ \langle \left(\lambda x.t_1\right)v, \theta \rangle &\to& \langle t_1, \theta[x \mapsto v] \rangle \\ \end{array} \\] 図2.1 対象言語

まあ、ジャバの前ではなんでもいいですわ。

3. Syntax - Sealed Classes, Records, String Templates

昔のジャバなら次のようにinterfaceをclassが継承していくようになるだろう(図3.1)。

3.1 ASTの定義
// V for variable, ord and eq
public interface Lambda<V> {
  // しれっとMapではなくてListにしているがご容赦してくれい
  public Lambda<V> eval(List<Pair<V, Lambda<V>>> env);
}

public class Abs extends Lambda<V> {
  V param;
  Lambda<V> body;
  Abs<V>(V param, Lambda<V> body) {
    this.param = param;
    this.body = body;
  }

  public Lambda<V> eval(List<Pair<V, Lambda<V>>> env) {
    // ......
  }
}

この方法には問題がある。 このinterfaceを継承するclassは際限なく増えてしまう。 このため、evalメソッドのディスパッチ時に不明なクラスが出てくる可能性がある。

そこで、ver17より導入されたSealed Classesを使う(図3.2)。

3.2 Sealed Classes
public sealed interface Lambda <V>
    permits  Var, Abs, App {
      public Lambda<V> eval(List<Pair<V, Lambda<V>>> env);
}

ワーオ! これでLambdaを継承するclassはVarAbsAppに絞ることができるんですね。

3.2再掲
public class Abs extends Lambda<V> {
  V param;
  Lambda<V> body;
  Abs<V>(V param, Lambda<V> body) {
    this.param = param;
    this.body = body;
  }

  // ......

こちらver16で導入されたRecordsを使うと簡単に書ける(図3.3)。

3.3 Records
public record Abs<V>(V param, Lambda<V> body) implements Lambda<V> {
  // ......
}

ワーオ! 便利ですね。 nameprivate finalになり、getter、equals、ちょっといい感じのtoStringがパラメータに対して自動生成される(図3.4)。

3.4 ver9からREPLもあるんやで
jshell> new Abs("x", new Var("x"))
|  Warning:
|  unchecked call to Var(V) as a member of the raw type Var
|  new Abs("x", new Var("x"))
|               ^----------^
|  Warning:
|  unchecked call to Abs(V,Lambda<V>) as a member of the raw type Abs
|  new Abs("x", new Var("x"))
|  ^------------------------^
// いい感じのtoString
$5 ==> Abs[param=x, body=Var[name=x]]

toStringが定義されていれば生成されない。 今回はpretty printのために手で書く(図3.5)。

3.5 AbsのtoString
public record Abs<V>(V param, Lambda<V> body) implements Lambda<V> {
  public String toString() {
    return "λ" + param + "." + body;
 }

  // ......
}

ところでよぉ、モダンジャバってのにはstring interpolationもねえのかよ。 あるんだよね。 ver22でPreviewとしてString Templatesという機能が導入されている(図3.6)。

3.6 String Templates
  public String toString() {
    return STR.`λ\{this.param}.\{this.body}`;
  }

このSTRちゅうのがtemplate processorと呼ばれるもので、デフォルトでimportされている3。 このtemplate processorが、渡される文字列の\{expr}をstringified exprとして埋め込んでくれる。 FMTというtemplate processorも提供されており、こちらはformat specifierが使える。

4. Eval - Switch Expressions, Pattern Matching

さあ構文木も定義できたしevalいくぞ。

Varはさくっと行く。 Varの評価時に環境から評価する値を取り出すときにver8から導入されたStream APIを使う(図4.1)。 もちろん同時期に入ったLambda Expressionを前提としている。 ver8は2014年だから他の講義で使ってたわ流石に。

4.1 Stream API
public record Var<V>(V name) implements Lambda<V> {
  // ......

  public Lambda<V> eval(List<Pair<V, Lambda<V>>> env) throws RuntimeException {
    // これね
    var e = env.stream().filter(p -> p.first().equals(this.name)).findFirst();
    if (e.isPresent()) {
      return e.get().second();
    } else {
      throw new RuntimeException(STR."var \{this.name} not found");
    }
  }
}

ってvarってナンデスカー!? これはver10で導入されたLocal Variable Type Inferenceである。 もうFoobar x = new Foobar()なんてアホみたいなこと書かんでええのや。 しかし、型推論ができるのはローカル変数のみであり、メソッドのシグネチャ等には使えないためちょっとだけかゆいところに手が届かない。 とはいえ学部時代のジャバからは大進化ですよ。

まあ見るべきはAppっしょ(図4.2)。

4.2 Appの評価
public record App<V>(Lambda<V> func, Lambda<V> arg) implements Lambda<V> {
  // ......

  public Lambda<V> eval(List<Pair<V, Lambda<V>>> env) throws RuntimeException {
    var func = this.func.eval(env);
    var arg = this.arg.eval( env);
    // switch expression - Java 14
    return switch (func) {
      // pattern matching - Java 21
      case Abs(V param, var body) -> {
        var newEnv = new ArrayList<>(env);
        newEnv.add(new Pair<>(param, arg));
        yield body.eval(newEnv);
      }
      case NativeFn(Function<Lambda<V>, Lambda<V>> fn) -> fn.apply(arg);
      default -> { throw new RuntimeException(STR."not a function: \{func}"); }
    };
  }

おいなんだこれ! 知ってるジャバじゃねえぞ!! まず、ver14でSwitch Expressionsが導入された。 あのなぁ、ジャバはポリモーフィズムがあってカプセル化があるオブジェクト指向の真の継承者なんだぞ。 なんだよswitch expressionって…。 しかもver21でPattern Matchingも導入された。 もはや関数型言語じゃないですか! オブジェクト指向で関数型…これは……オーキャモ…? …まあ大変便利です、ありがとうモダジャバ4

Switch expressionはcase内でyieldを使うことで値を返すことができる。 パターンマッチングは言わずもがな、しかし型注釈が必要になるので注意。

5. main - Implicityly Declared Classes and Instance Main Methods

あとはmainから呼ぶだけ。 ここにも驚きがあるのがモダンジャバ(図5.1)。

5.1 main
void main() {
  var id = new Abs("x", new Var("x"));
  System.out.println(id);
}

なんすかこれはpublic static wow wow yeah void main(String[] args)じゃねえの? クラス定義はどこ行ったんスカ?

これはver21からpreviewで入っているImplicityly Declared Classes and Instance Main Methods5による恩恵。 まずmainが簡潔に書けるようになった。 publicおよびstaticプロパティが緩和され、コマンドライン引数を受け取るargsも省略可能になった。

次にクラスなしについて。 トップレベルクラス定義がないと無名メソッドが生成され、そのインスタンスメソッドとしてmainが呼ばれるようになった。 これがstaticなくてOKな理由である。 Implicitly declared classesは主に、ver10で導入されたLaunch Single-File Source-Code Programsによるスクリプティングを、より簡単におこなうためのものである。 サク書き用ですね。 なので、package指定が無い場合にのみ使うことができ、一方jarに固めたりするときはエンドポイントを指定できないのでclassは変わらず必要になる。

6. おわりに

Javaのモダナイゼーションにウズウズしてしまい、ver22のリリースでとうとう爆発した結果、筆をとることとなった。 ver21で導入されたVirtual Threadsも触りたいが今回は見送る。 ver22の新機能は主に[6]を参考にした。


  1. T大はイバラキスタン北部にあるT市の大学 

  2. 1つ前の代ではまくらパターンという謎の用語が使われる謎の教科書が用いられていた https://www.amazon.co.jp/gp/customer-reviews/R1YHWYDK2SZFLE/ 

  3. OCamlでいうところのStdlibみたいなzero configで使えるやつってJavaでなんていうんすか 

  4. もちろんモダンジャバの略 

  5. ver21では"Unnamed Classes and Instance Main Methods"という名前だった 

  6. Java 22新機能まとめ #Java - Qiita