1
0

JavaのOptionalをgolangにportする

Last updated at Posted at 2024-04-19

はじめに

Java さわってますか?私は10年近くさわってません。JDK 8 Release Notesを見ると2014年5月15日にリリースされているので大体10年です。

この10年位は golang をさわってます。

Javaの勉強もしなければなと思いつつも、作りたいアプリケーションも特にありません。ドキュメントを読んでるだけというのも長続きしないかなと思ってます。そんな時に以下の2つのブログを読みました。

これらを読んで「 Java の Optional を golang に移植したら、 Java のコードも読むし勉強になるかな?」と思いました。そんなわけでゆるゆると Java の Optional を golang に移植していこうと思います。その際に得た知見をゆるゆるとブログに書いていきます。

Java の Optional の書き方はあまりgolang的なものではないなと思ってます。なので、「これがあれば幸せになれるよ」的なものを作ろうとしている訳ではありません。あくまでネタとして、ゆるゆる見て頂ければと思います。

移植対象

移植対象の Optional はJDK 21 にしました。具体的には以下のソースコードです。

また JavaDoc は以下のものになります。

また Optional ではいかの interface が利用されています。

これらも適宜実装していきます。前振りが長くなりましたが、ここから本編です。

Functionの実装

Optional の移植といいつつ、まずは Function を実装します。 Optional のメソッドを実装するために必要なため、 Function から始めます。Javaのソースコードはこちらです。 以下にコメントを削除したコードを記載します。

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

このコードでgolangでは直接対応出来無い部分は以下の3つになります。

  1. メソッドに対するgenericの適用
  2. default method の実装
  3. static method の実装

この中で特に問題になるのが 1 です。例えば以下の interface を golang で定義したとします。

type Function[T any, R any, V any] interface {
	Apply(T) R
	AddThen(Function[R, V, T]) Function[T, V, R]
    Compose(Function[V, T, R]) Function[V, R, T]
}

形は Java の Function と似ていますが、実際は全く異るものです。

Java の場合は composeaddThen というメソッドに対して V という型パラメータが指定されています。このため、この VcomposeaddThen が利用される時に決定されます。複数の個所で異る V と使っても問題有ません。ある場所では Integer でも、別の場所では String を利用できます。勿論、 composeaddThen では別の V を利用できます。

これに対して golang では VFunction interface に対して指定されています。このため、 Function interface を実装する構造体を実装する際に決定されなければなりません。 Java とは異り、 ComposeAddThen は同じ V を使わなければなりません。また ComposeAddThen は使われる全ての場所で同じ V を使わなければなりません。

これは、 golang がコンパイル時に型パラメータと対応した構造体やinterfaceを生成しているのに対し、 Java はコンパイル時に型のチェックを行っているだけという違いから生れるものだと思います。 golang では設定されたそれぞれの型パラメータに対して対応する実体が生成されます。例えば slices.Concat に対して

  1. slices.Concat([]int{1, 2}, []int{3})
  2. slices.Concat([]string{"foo", "bar"}, []string{"buz"})

は別の関数が生成されます。それにたいして Java の ArrayListでは、

  1. new ArrayList<Integer>()
  2. new ArrayList<String>()

をした場合、別のクラスは生成されません。あくまで ArrayList<Object> が存在するだけです。それぞれは利用されている場所で IntegerString のみを受け付けるようコンパイル時にチェックされるだけです。

実際にバイトコードではこの情報は削除されます。この削除をType erasureと呼びます。この結果、 Java ではメソッドに型パラメータが指定されていたとしても、利用されないならば、無視してコンパイルできることになります。 andThen が使われない場合は、andThen の戻値の Function<T, R>R 部分は存在しなくてもいいわけです。しかし golang の場合は、利用しないとしてもその型パラメータの指定は必要になります。それがなければ interface を生成できないからです。

長々書きましたが、結論としては golangだと andThen や compose 相当のメソッドは定義できないので、関数にする という結論になりました。

1.の メソッドに対するgenericの適用 だけでかなりながくなりまし。 2. default method の実装 については今回はdefault methodを使わなくて済んだので、今後必要となった時に考えます。3. static method の実装 については関数で対応します。

上記をふまえて実装した JavaFunction に対応する機能が以下のものになります。

Functionとの相違点をまとめると

  1. interface名は Applier にした
    • 残ったメソッドが apply のみだったため、golangの慣習に従った
  2. Java のメソッド andThencomposeCompose 関数に纏めた
    • メソッドだから前に付けるか後ろに付けるかが問題になる。関数ならば問題にならない
  3. satic method の identity も関数の Identity にした
  4. NewApplierFuncNewApplierFuncWithNoError を導入し、関数を簡単に Applier interface へ適合できるようにした
    • Javaの場合は FunctionalInterface を満していればメソッドを簡単に Function interfaceに代入できるが、 golang ではそれができないため

まとめ

Function の移植だけでもかなりの調べることがありました。色々調べているつもりでしたが、全然足りていないと実感しました。公式ドキュメントをちゃんと調べるいい機会でした。

今後は Predicate などを移植していきます。Javaとgolangの仕様を調べないといけないので、楽しみです。

参考文献

つづき

JavaのOptionalをgolangにportする - Method Reference と Method values

1
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
1
0