これは何か
kotlinの予約語、infixってなんやねんって思っちゃったので定義や作り方を調べてみました。
目次
- なんとかfix
- 標準ライブラリの例:「to」
- infixの関数を定義する
- 定義したinfixの関数をKotlinから呼び出す
- 定義したinfixの関数をJavaから呼び出す
- infix関数を逆アセンブルする
- 参考
なんとかfix
そもそも、infixって英単語に馴染みがないですよね。
試しにGoogle翻訳にかけると、「中置」って返ってくる。
ここで気づきます、なんとかfixの一つだ、と。他にもよく使ってるなんとかfixがありますよね、そういえばprefixとかsuffixとか...
中置きの文字通り、オブジェクトや値の間にinfix関数を置いて、挟まれた2つのオブジェクトや値を引数に処理を行う関数を定義できるKotlinの機能である。
標準ライブラリの例:「to」
標準ライブラリのinfix関数 to
は、下記のように呼び出して Pair
インスタンスを生成する。
val pair: Pair<String, String> = "key" to "value"
メソッドは、下記のように拡張関数として定義されている。
/**
* Creates a tuple of type [Pair] from this and [that].
*
* This can be useful for creating [Map] literals with less noise, for example:
* @sample samples.collections.Maps.Instantiation.mapFromPairs
*/
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
参考:Tuples.kt
それでは、infix関数を自作してみたい。
infixの関数を定義する
よく遭遇するような、「同じようなオブジェクトを持っている、似ているオブジェクトのインスタンスに値を詰め替える」ケースを想定する。
ここでは、Customer
クラスの値に履歴シーケンスをくっつけてCustomerHistory
クラスのインスタンスを生成し、それらのインスタンスをジェネリクスに含むPair
インスタンスを返す関数を作る。
まずはインプットと合成の結果となるデータクラスを定義。
data class Customer (
var id: String,
var name: String
)
data class CustomerHistory (
var id: String,
var name: String,
var seq: Int
)
元ネタであるCustomer
から、シーケンス付きでCustomerHistory
インスタンスを生成するinfix関数を、以下のように定義する。
/**
* 拡張関数内部でレシーバをthisとして扱っている。シーケンスは関数の引数として受け取る。
*/
infix fun Customer.mapToHistory(seq: Int): Pair<Customer, CustomerHistory> = Pair(
this,
CustomerHistory(
this.id,
this.name,
seq
)
)
これで準備完了。さて、Kotlinコード、およびJavaコードから定義したinfix関数を呼んでみる。
定義したinfixの関数をKotlinから呼び出す
上記で定義した自作infix関数を呼び出す。
// 元ネタ。
val customer: Customer = Customer("00001", "Eron Musk")
// 元ネタと履歴オブジェクトのPairをシーケンス付きで返す。
val pair: Pair<Customer, CustomerHistory> = customer mapToHistory 2
定義した関数が、例としてよくなかったのでありがたみがわかづらいけども、オブジェクトや値の間に定義した関数を置くことで結果を得られる。
定義したinfixの関数をJavaから呼び出す
Kotlinの相互運用性により、Javaからも呼び出しが可能である。
同じような呼び出しのスニペットを、Javaでもやってみると
/**
* 戻り値は、Kotlinのライブラリに含まれるkotlin.Pairをimportして定義する。
*/
public static void printHistory(Customer customer) {
Pair<Customer, CustomerHistory> customerHistory = InfixKt.mapToHistory(customer, 2);
}
やはりなんとなく予想はついてたけど、Javaからみるとinfix関数はstaticメソッドになっている。
infix関数を逆アセンブルする
定義したKotlinファイルはkotlinc
でコンパイルした。
Javaからの呼び出しを見ることで命令手続きは大方想像がつくんだけども、気になる。
ということで、コンパイルされたKotlinファイル(クラスファイル)を逆アセンブルしてみる。
mac:classes user$ javap InfixKt.class
Compiled from "Infix.kt"
public final class InfixKt {
public static final kotlin.Pair<Customer, CustomerHistory> mapToHistory(Customer, int);
}
めちゃくちゃ想像どおりだった...クラスのない、もしくは複数クラスをもつKotlinファイルは、コンパイルされると<file name>Ktと命名される。もとのファイル名は Infix.kt
だった。
逆アセンブルの結果を見ると、finalなクラスに内包されたstaticメソッドになっている。
2020-07-02:追記
staticメソッドになっているのは、自作した関数が拡張関数であるためでした。
javap
にオプションをつけて、もう少し詳しい情報を出力してみる。
mac:classes user$ javap -c -l -p InfixKt.class
Compiled from "Infix.kt"
public final class InfixKt {
public static final kotlin.Pair<Customer, CustomerHistory> mapToHistory(Customer, int);
Code:
0: aload_0
1: ldc #10 // String $this$mapToHistory
3: invokestatic #16 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: new #18 // class kotlin/Pair
9: dup
10: aload_0
11: new #20 // class CustomerHistory
14: dup
15: aload_0
16: invokevirtual #26 // Method Customer.getId:()Ljava/lang/String;
19: aload_0
20: invokevirtual #29 // Method Customer.getName:()Ljava/lang/String;
23: iload_1
24: invokespecial #33 // Method CustomerHistory."<init>":(Ljava/lang/String;Ljava/lang/String;I)V
27: invokespecial #36 // Method kotlin/Pair."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
30: areturn
LineNumberTable:
line 12: 6
line 13: 10
line 14: 11
line 15: 15
line 16: 19
line 17: 23
line 14: 24
line 12: 27
line 19: 30
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 $this$mapToHistory LCustomer;
0 31 1 seq I
}
やはり、コンパイルされたあとも見知ったJavaのオペコードになっているようである。
3行まとめ
- infixとは、中置きを指す言葉で接頭辞/接尾辞の間に位置するようなもの。
- infix関数は、値やオブジェクトの間に置いて処理を行う関数である。
- Javaからみると、staticメソッドのシンタックスシュガーのようになっている。
参考
ウェブサイト
中置き記法 - Kotlinリファレンス