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.
要約すると
- 機能性(functionality)をメソッドの引数として扱える
- 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種があります。
- static method に対する参照
- 特定のオブジェクトの instance method に対する参照
- 任意のオブジェクトに対する特定の型の instance method に対する参照
- コンストラクタに対する参照
それぞれの詳細は公式ドキュメントの 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 必要?」ということになってきます。全て関数型の変数に代入してしまえばいいのではないかと。
というわけで、そうしました。 修正後のコードは以下のものになります。
前回からの相違点は以下の通りです。
-
Applier
interface を削除し、Function
型を導入した-
Function
型の定義はfunc(T) (U, error)
です
-
- Compose関数の引数と戻り値は両方とも
Applier
からFunction
に変更した - functionディレクトリの下にさらにfunctionディレクトリを作成しました
- 今後、Javaの
Predicate
やConsumer
を実装する際に、同じ名前の関数を作りたいことがおこりそうなので、 package をわけた
- 今後、Javaの
修正したバージョンで最初の例を書き直すと以下の様になります。
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 も知らなかった機能を知れたので有意義な時間になりました。実装方針も少しずつ固まってきているので、 今後は Predicate
は Consumer
を実装していきたいと思います。