1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-05-13

JavaのOptionalをgolangにportする が他の記事に比べて見られてます。といっても3桁程度ですが。タイトル詐偽的な状況になってしまったのかなと反省してます。今回も大した事は書いてないのでゆるゆるお付き合いください。

前回までのあらまし

  • Javaを勉強するために、golangでJavaのOptionalを実装してみます
  • Optionalをいきなり実装できないので、まずFunctionを実装します
  • golangでApplierを作ってみました

今回のあらすじ

今回は、「Applierを作ったけど、Method valuesがあるからインターフェースいらなかった。Method valuesを使えばJavaのFunctional interfaceみたいなことができる。」という話をします。

なぜ Applier interface を定義したのか

Applier interface を定義した理由は、golang では method を変数に代入できないと思っていたからです。このため、methodをApplierに適合させるためには以下の様な方法を考えていました。

package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/dairyo/j2g/java/util/function"
)

type builder struct {
	strings.Builder
}

func (b *builder) Apply(s string) (int, error) {
	return b.WriteString(s)
}

func main() {
	sb1 := &strings.Builder{}
	f1 := function.Compose(
		function.NewApplierFuncWithNoError(strconv.Itoa),
		// methodを関数リテラルで囲まないといけないとおもっていた。
		function.NewApplierFunc(func(s string) (int, error) { return sb1.WriteString(s) }),
	)
	f1.Apply(10)
	fmt.Println(sb1.String())

	sb2 := &builder{}
	f2 := function.Compose(
		function.NewApplierFuncWithNoError(strconv.Itoa),
		// または集約を使って、Applierに適合させる。
		sb2,
	)
	f2.Apply(10)
	fmt.Println(sb2.String())
}

しかし、上のような冗長なことをしなくても、 Method values を使えば関数変数に直接 method を代入できます。

Java の Method References

Lambda

golang の Method values の話をする前に対応する Java の Lambda と Method Reference の説明をします。

Java の Lambda は JDK 8 で導入された機能です。リリースノートでは以下の様に説明されています。

Lambda Expressions, a new language feature, has been introduced in this release. They enable you to treat functionality as a method argument, or code as data. Lambda expressions let you express instances of single-method interfaces (referred to as functional interfaces) more compactly.

要約すると

  1. 機能性(functionality)をメソッドの引数として扱える
  2. functional interfaceという1つのメソッドのみを持つ interface の instance を簡単に表現できる

Lambda の説明のためにまず functional interface について説明します。functional interfaceとは抽象メソッドが1つだけあるinterfaceです。制約は抽象メソッドが1つだというものなので static method や defalut method が複数存在しても問題ありません。

JavaのFuctionは抽象メソッドが apply しかないので functional interface です。このため Lambda を代入できます。

import java.util.function.Function;

public class Main  {
    public static void main(String[] args) {
        // Function に Lambda を代入
        Function<String, Integer> f = s -> s.hashCode();
        System.out.println(f.apply("foo"));
    }
}

しかし、上の書き方だと String.hasCode を使いたいだけなのに Lambda を書かなければならず少し面倒です。そこで使われるのが Method Reference です。

Method Reference

Method Reference はメソッドへの呼出しを Lambda と読み替えてくれる機能です。以下に例を記載します。

import java.util.function.Function;

public class Main  {
    public static void main(String[] args) {
        // Method Reference を利用
        Function<String, Integer> f = String::hashCode;
        System.out.println(f.apply("foo"));
    }
}

Method Reference は以下の4種があります。

  1. static method に対する参照
  2. 特定のオブジェクトの instance method に対する参照
  3. 任意のオブジェクトに対する特定の型の instance method に対する参照
  4. コンストラクタに対する参照

それぞれの詳細は公式ドキュメントの Method Referencesをご覧下さい。今回利用したのは 3 の 任意のオブジェクトに対する特定の型の instance method に対する参照 になります。

ここまでで Lambda のリリースノートの要約で書いた functional interfaceという1つのメソッドのみを持つ interface の instance を簡単に表現できる は分かったかと思います。最後に上のものを Lambda や Method Reference を使わないで書きます。 Lambda や Method Reference と言っても関数自体を作っているわけではありません。 Java はいくつかの基本型を除けばクラス型しかないので、 Lambda や Method Reference で作られるものも、結局無名クラスでしかないのです。

import java.util.function.Function;

public class Main  {
    public static void main(String[] args) {
        // Method Reference を利用
        var f = new Function<String, Integer>() {
            public Integer apply(String s) {
                return s.hashCode();
            }
        };
        System.out.println(f.apply("foo"));
    }
}

上の例を見るとわかるように、 Lambda 相当のコードは Java7 以前も書けたのです。ただし、 Lambda とそれに付随する機能が追加された事でより簡単に書けるようになったことがわかると思います。その点を明確にするため、要約のもう一つ 機能性(functionality)をメソッドの引数として扱える を説明したいと思います。 なぜ Applier interface を定義したのか の最初に書いた golang のサンプルを Java を使ってかいてみます。

import java.util.function.Function;

public class Main  {
    public static void main(String[] args) {
        // Method Reference を利用
        Function<Integer, String> f = i -> i.toString();
        var sb = new StringBuilder();
        sb = f.andThen(sb::append).apply(10);
        System.out.println(sb.toString());
    }
}

StringBuilder の append メソッドを Method Reference を使って最初の f のオブジェクトに直接渡せます。これが 機能性(functionality)をメソッドの引数として扱える という意味だとおもっています。ちなみに andThen の引数で使われている Method Reference は 特定のオブジェクトの instance method に対する参照 になります。

golang の Method Values

golangの関数

上で書いたように Java には関数自体はなく、無名クラスを簡単に作成することで擬似的に関数を表現しています。これに対して golang は関数が存在します。そして第一級関数です。このため golang の関数は変数に代入できます。例えば以下の様なコードを書けます。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	f1 := func(i, j int) int { return i + j }
	f2 := strconv.Itoa

	fmt.Println(f1(1, 1))
	fmt.Println(f2(10))
}

これは golang の利用者ならば大体の方がしっていることだと思います。この関数の変数への代入ですが、メソッドでも可能です。それを Method Values といいます。

Method Values

Method Values を使うと以下の様なコードを書けます。

package main

import (
	"fmt"
	"strings"
)

func main() {
	b := &strings.Builder{}
	var f func(string) (int, error) = b.WriteString
	f("foo")
	fmt.Println(b.String())
}

関数変数に入れる事を明示するために var f func(string) (int, error) = b.WriteString と書いていますが、もちろん f := b.WriteString でも大丈夫です。これは Java の Method Reference の 2. 特定のオブジェクトの instance method に対する参照 に対応すると思います。

Method Expressions

golang には Method Expressions というものもあり、こちらは3. 任意のオブジェクトに対する特定の型の instance method に対する参照 に対応すると思います。以下に例を記載します。

package main

import (
	"fmt"
	"strings"
)

func main() {
	var f func(*strings.Builder, string) (int, error) = (*strings.Builder).WriteString
	b := &strings.Builder{}
	f(b, "foo")
	fmt.Println(b.String())
}

1. static method に対する参照4. コンストラクタに対する参照 は関数を関数変数に代入することで対応すると思うので、 Java の Method Reference 相当の処理は全て golang で出来ることになります。

Applier interface の修正

こうなると「そもそも Applier interface 必要?」ということになってきます。全て関数型の変数に代入してしまえばいいのではないかと。

というわけで、そうしました。 修正後のコードは以下のものになります。

前回からの相違点は以下の通りです。

  1. Applier interface を削除し、 Function 型を導入した
    • Function 型の定義は func(T) (U, error) です
  2. Compose関数の引数と戻り値は両方とも Applier から Function に変更した
  3. functionディレクトリの下にさらにfunctionディレクトリを作成しました
    • 今後、Javaの PredicateConsumer を実装する際に、同じ名前の関数を作りたいことがおこりそうなので、 package をわけた

修正したバージョンで最初の例を書き直すと以下の様になります。

package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/dairyo/j2g/java/util/function/function"
)

func main() {
	sb := &strings.Builder{}
	f1 := function.Compose(
		function.WrapNoErrFunc(strconv.Itoa),
		sb.WriteString,
	)
	f1(10)
	fmt.Println(sb.String())
}

多少簡潔になりました。また引数の渡し方が一意に決まりました。

まとめ

二回目でも Function のportが終らないというゆるゆるした進みになっています。しかし、 Java の Lambda の勉強が出来て、 golang も知らなかった機能を知れたので有意義な時間になりました。実装方針も少しずつ固まってきているので、 今後は PredicateConsumer を実装していきたいと思います。

参考文献

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?